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

# Lens Pricing API

> Manage optical lens prices by clusters and axes with bulk pricing operations and table-optimized formats

# Lens Pricing API Documentation

This document describes the lens pricing endpoints for managing optical lens prices by clusters and axes.

## Endpoint Details

**Base URL:** `https://joptic.jethings.com`

### Required Headers

```
x-store-id: <store_id>
Authorization: Bearer <token>
```

***

## Authentication

All endpoints require store authentication via the `@StoreId()` decorator, which automatically extracts the store ID from the request context.

***

## Get All Lens Clusters

Get all unique lens clusters available for the current store.

**Endpoint:** `GET /lens-pricing/clusters`

### Query Parameters

None

### Success Response

```json theme={null}
{
  "clusters": [
    {
      "name": "1.56 HMC",
      "itemCount": 50
    },
    {
      "name": "1.50 BB",
      "itemCount": 30
    },
    {
      "name": "1.67 HC",
      "itemCount": 25
    }
  ]
}
```

### Response Fields

* `clusters` (array) - Array of cluster objects
  * `name` (string) - Cluster name (e.g., "1.56 HMC")
  * `itemCount` (number) - Total number of items in each cluster

<Note>
  Cluster names exclude color information (e.g., "1.50 PhGy BB" becomes "1.50 BB"). Clusters are sorted numerically. `itemCount` represents the total number of items in each cluster.
</Note>

### Example Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/clusters" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://joptic.jethings.com/lens-pricing/clusters", {
    method: "GET",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
    },
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/clusters"

  response = requests.get(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>"
      }
  )

  print(response.json())
  ```
</CodeGroup>

### Possible Errors

* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `500 Internal Server Error` - Server error

***

## Get Items with Pricing (Paginated)

Get all items with pricing for a specific cluster with pagination support.

**Endpoint:** `GET /lens-pricing/items`

### Query Parameters

| Parameter     | Type     | Required | Default | Description                            |
| ------------- | -------- | -------- | ------- | -------------------------------------- |
| `cluster`     | `string` | ✅ Yes    | —       | The lens cluster, e.g., "1.56 HMC"     |
| `priceListId` | `string` | ✅ Yes    | —       | The price list ID to fetch prices from |
| `page`        | `number` | No       | `1`     | Page number (min: 1)                   |
| `limit`       | `number` | No       | `10`    | Items per page (min: 1, max: 100)      |

### Success Response

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListType": "sell",
  "priceList": {
    "id": "price_list_123",
    "name": "Selling Prices"
  },
  "data": [
    {
      "itemId": "item_123",
      "itemName": "1.56 HMC +0.00 -0.00",
      "sph": "+0.00",
      "cyl": "-0.00",
      "price": 800,
      "priceId": "price_123",
      "hasPrice": true,
      "isActive": true
    },
    {
      "itemId": "item_124",
      "itemName": "1.56 HMC +0.25 -0.25",
      "sph": "+0.25",
      "cyl": "-0.25",
      "price": 850,
      "priceId": "price_124",
      "hasPrice": true,
      "isActive": true
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 50,
    "totalPages": 3,
    "hasNext": true,
    "hasPrev": false
  },
  "itemsWithPrice": 45,
  "itemsWithoutPrice": 5
}
```

### Response Fields

* `cluster` (string) - The cluster name
* `priceListType` (string) - The price list type derived from price list flags ("sell", "buy", or "diff")
* `priceList` (object) - Price list information
  * `id` (string) - Price list ID
  * `name` (string) - Price list name
* `data` (array) - Array of items with pricing
  * `itemId` (string) - Item ID
  * `itemName` (string) - Full item name
  * `sph` (string) - Sphere value with sign (e.g., "+0.25")
  * `cyl` (string) - Cylinder value with sign (e.g., "-0.50")
  * `price` (number | null) - Price value or null if not set
  * `priceId` (string | null) - Price ID if price exists
  * `hasPrice` (boolean) - Whether a price exists for the item
  * `isActive` (boolean) - Whether the item is active
* `pagination` (object) - Pagination metadata
* `itemsWithPrice` (number) - Count of items with prices
* `itemsWithoutPrice` (number) - Count of items without prices

<Note>
  Items are sorted by SPH and CYL values numerically. `sph` and `cyl` values include their signs (e.g., "+0.25", "-0.50"). `price` is `null` if no price is set for the item. `hasPrice` indicates whether a price exists for the item.
</Note>

### Example Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/items?cluster=1.56%20HMC&priceListId=price_list_123&page=1&limit=20" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```

  ```javascript JavaScript theme={null}
  const params = new URLSearchParams({
    cluster: "1.56 HMC",
    priceListId: "price_list_123",
    page: "1",
    limit: "20"
  })

  const response = await fetch(`https://joptic.jethings.com/lens-pricing/items?${params}`, {
    method: "GET",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
    },
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/items"

  params = {
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "page": 1,
      "limit": 20
  }

  response = requests.get(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>"
      },
      params=params
  )

  print(response.json())
  ```
</CodeGroup>

### Possible Errors

* `400 Bad Request` - Missing or invalid cluster parameter
* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `404 Not Found` - Price list not found for the specified type
* `500 Internal Server Error` - Server error

***

## Get Items as Table/Matrix Format

Get items with pricing organized in a matrix format grouped by sign combinations. This endpoint is optimized for table rendering in the frontend.

**Endpoint:** `GET /lens-pricing/items/table`

### Query Parameters

| Parameter     | Type     | Required | Default  | Description                            |
| ------------- | -------- | -------- | -------- | -------------------------------------- |
| `cluster`     | `string` | ✅ Yes    | —        | The lens cluster, e.g., "1.56 HMC"     |
| `priceListId` | `string` | ✅ Yes    | —        | The price list ID to fetch prices from |
| `format`      | `string` | No       | `matrix` | Response format: "matrix" or "array"   |

### Success Response

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListType": "sell",
  "priceList": {
    "id": "price_list_123",
    "name": "Selling Prices"
  },
  "matrices": {
    "pp": {
      "axes": {
        "sph": [0, 0.25, 0.5, 0.75, 1, 1.25],
        "cyl": [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5]
      },
      "prices": {
        "0|0": 800,
        "0|0.25": 800,
        "0|0.5": 800,
        "0|1.5": 1250,
        "0.5|0.5": 800,
        "0.5|0.75": 800,
        "1.25|1.25": 800,
        "1.25|1.5": 1250
      }
    },
    "nn": {
      "axes": {
        "sph": [0, 0.25, 0.5, 0.75],
        "cyl": [0, 0.25, 0.5]
      },
      "prices": {
        "0|0": 900,
        "0.5|0.5": 1000
      }
    },
    "np": {
      "axes": {
        "sph": [0, 0.25, 0.5, 0.75],
        "cyl": [0, 0.25, 0.5, 0.75]
      },
      "prices": {
        "0|0": 850,
        "0.75|0.75": 1100
      }
    }
  }
}
```

### Response Fields

* `cluster` (string) - The cluster name
* `priceListType` (string) - The price list type derived from price list flags ("sell", "buy", or "diff")
* `priceList` (object) - Price list information
  * `id` (string) - Price list ID
  * `name` (string) - Price list name
* `matrices` (object) - Price matrices grouped by sign combinations (matrix format)
* `data` (object) - Price data grouped by sign combinations (array format)
  * `pp` (object) - Positive SPH (+) / Positive CYL (+) matrix
  * `nn` (object) - Negative SPH (-) / Negative CYL (-) matrix
  * `np` (object) - Negative SPH (-) / Positive CYL (+) matrix
    * `axes` (object) - Available axis values
      * `sph` (array) - Available SPH values (sorted numerically)
      * `cyl` (array) - Available CYL values (sorted numerically)
    * `prices` (object) - Price map using `"sph|cyl"` keys

### Sign Combinations

<CardGroup cols={3}>
  <Card title="pp" icon="plus">
    **Positive SPH (+) / Positive CYL (+)**

    <br />

    Both values are positive
  </Card>

  <Card title="nn" icon="minus">
    **Negative SPH (-) / Negative CYL (-)**

    <br />

    Both values are negative
  </Card>

  <Card title="np" icon="layers">
    **Negative SPH (-) / Positive CYL (+)**

    <br />

    Mixed signs
  </Card>
</CardGroup>

<Note>
  Only non-empty matrices are included in the response. Price keys use the format `"sph|cyl"` with absolute values (e.g., `"0.25|0.5"`). Prices are `null` if not set for that item. Axes arrays are sorted numerically in ascending order. This format is optimized for rendering pricing tables in the frontend.
</Note>

### Example Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/items/table?cluster=1.56%20HMC&priceListId=price_list_123&format=matrix" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```

  ```javascript JavaScript theme={null}
  const params = new URLSearchParams({
    cluster: "1.56 HMC",
    priceListId: "price_list_123",
    format: "matrix"
  })

  const response = await fetch(`https://joptic.jethings.com/lens-pricing/items/table?${params}`, {
    method: "GET",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
    },
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/items/table"

  params = {
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "format": "matrix"
  }

  response = requests.get(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>"
      },
      params=params
  )

  print(response.json())
  ```
</CodeGroup>

### Possible Errors

* `400 Bad Request` - Missing or invalid cluster or priceListId parameter
* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `404 Not Found` - Price list not found or does not belong to the store
* `500 Internal Server Error` - Server error

***

## Get Price Difference Between Two Price Lists

Get the profit margin (difference) between two price lists for a cluster. Returns profit in either currency or percentage format, grouped by sign combinations (pp, nn, np). This endpoint is useful for analyzing profit margins and pricing strategies.

**Endpoint:** `GET /lens-pricing/diff`

### Query Parameters

| Parameter      | Type     | Required | Default    | Description                                                |
| -------------- | -------- | -------- | ---------- | ---------------------------------------------------------- |
| `cluster`      | `string` | ✅ Yes    | —          | The lens cluster, e.g., "1.56 HMC"                         |
| `priceListId1` | `string` | ✅ Yes    | —          | First price list ID (typically the higher/sell price list) |
| `priceListId2` | `string` | ✅ Yes    | —          | Second price list ID (typically the lower/buy price list)  |
| `mode`         | `string` | No       | `currency` | Profit calculation mode: "currency" or "percentage"        |

**Mode Options:**

* `currency` - Returns profit as the difference in currency units (priceList1 - priceList2)
* `percentage` - Returns profit as a percentage ((priceList1 - priceList2) / priceList2 \* 100)

### Success Response

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListType": "diff",
  "priceList": {
    "id": "price_list_123",
    "name": "Selling Prices"
  },
  "data": {
    "pp": [
      { "x": 0.0, "y": 0.0, "value": 200 },
      { "x": 0.0, "y": 0.25, "value": 200 },
      { "x": 0.0, "y": 0.5, "value": 200 },
      { "x": 0.5, "y": 0.5, "value": 200 },
      { "x": 1.25, "y": 1.25, "value": 200 },
      { "x": 1.25, "y": 1.5, "value": 450 }
    ],
    "nn": [
      { "x": 0.0, "y": 0.0, "value": 150 },
      { "x": 0.5, "y": 0.5, "value": 200 }
    ],
    "np": [
      { "x": 0.0, "y": 0.0, "value": 175 },
      { "x": 0.75, "y": 0.75, "value": 300 }
    ]
  }
}
```

### Response Fields

* `cluster` (string) - The cluster name
* `priceListType` (string) - Always "diff" for difference calculations
* `priceList` (object) - Price list information (uses sell price list as reference)
  * `id` (string) - Price list ID
  * `name` (string) - Price list name
* `data` (object) - Profit data grouped by sign combinations
  * `pp` (array, optional) - Positive SPH (+) / Positive CYL (+) profit data
  * `nn` (array, optional) - Negative SPH (-) / Negative CYL (-) profit data
  * `np` (array, optional) - Negative SPH (-) / Positive CYL (+) profit data
    * Each array contains objects with:
      * `x` (number) - Sphere (SPH) value
      * `y` (number) - Cylinder (CYL) value
      * `value` (number | null) - Profit value (currency or percentage)

### Profit Calculation Logic

<AccordionGroup>
  <Accordion title="Currency Mode" icon="dollar-sign">
    **Currency Mode** (`mode=currency`)

    Profit is calculated as:

    ```
    profit = priceList1 - priceList2
    ```

    **Special Cases:**

    * If only priceList1 exists: `profit = priceList1`
    * If only priceList2 exists: `profit = -priceList2` (negative difference)
    * If neither exists: `profit = 0`
    * If both exist: `profit = priceList1 - priceList2`

    **Example:** If priceList1 is 800 and priceList2 is 600, profit = 200
  </Accordion>

  <Accordion title="Percentage Mode" icon="percent">
    **Percentage Mode** (`mode=percentage`)

    Profit percentage is calculated as:

    ```
    profitPercentage = ((priceList1 - priceList2) / priceList2) * 100
    ```

    **Special Cases:**

    * If only priceList1 exists: `profitPercentage = 100%` (100% profit margin)
    * If only priceList2 exists: `profitPercentage = -100%` (-100% difference)
    * If neither exists: `profitPercentage = 0%`
    * If both exist and priceList2 ≠ 0: `profitPercentage = ((priceList1 - priceList2) / priceList2) * 100`
    * If priceList2 = 0: Division by zero is avoided, returns appropriate default

    **Example:** If priceList1 is 800 and priceList2 is 600, profit = ((800 - 600) / 600) \* 100 = 33.33%
  </Accordion>

  <Accordion title="Missing Prices" icon="alert-circle">
    **Handling Missing Prices**

    The system handles cases where prices might be missing:

    * **Only priceList1 exists**: Treated as full profit (currency: priceList1, percentage: 100%)
    * **Only priceList2 exists**: Treated as potential cost (currency: -priceList2, percentage: -100%)
    * **Neither exists**: Returns 0 (currency) or 0% (percentage)
    * **Both exist**: Calculates actual difference

    This ensures meaningful profit analysis even with incomplete pricing data.
  </Accordion>

  <Accordion title="Sign Combinations" icon="layers">
    **Sign Combination Grouping**

    Profit is calculated separately for each sign combination:

    * **pp**: Positive SPH (+) / Positive CYL (+)
    * **nn**: Negative SPH (-) / Negative CYL (-)
    * **np**: Negative SPH (-) / Positive CYL (+)

    Only non-empty combinations are included in the response. Axes are merged from both price lists to ensure complete coverage.
  </Accordion>
</AccordionGroup>

<Note>
  The response format uses array structure (x, y, value) optimized for frontend table rendering. The `x` coordinate represents SPH value, `y` coordinate represents CYL value, and `value` represents the calculated profit. Axes are automatically merged from both price lists to include all combinations where either price exists. The `priceListType` field is set to "diff" for difference calculations.
</Note>

### Example Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/diff?cluster=1.56%20HMC&priceListId1=price_list_123&priceListId2=price_list_456&mode=currency" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```

  ```javascript JavaScript theme={null}
  const params = new URLSearchParams({
    cluster: "1.56 HMC",
    priceListId1: "price_list_123",
    priceListId2: "price_list_456",
    mode: "currency"
  })

  const response = await fetch(`https://joptic.jethings.com/lens-pricing/diff?${params}`, {
    method: "GET",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
    },
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/diff"

  params = {
      "cluster": "1.56 HMC",
      "priceListId1": "price_list_123",
      "priceListId2": "price_list_456",
      "mode": "currency"
  }

  response = requests.get(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>"
      },
      params=params
  )

  print(response.json())
  ```
</CodeGroup>

### Example: Get Profit as Percentage

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/diff?cluster=1.56%20HMC&priceListId1=price_list_123&priceListId2=price_list_456&mode=percentage" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```
</CodeGroup>

### Example Response: Currency Mode

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListType": "diff",
  "priceList": {
    "id": "price_list_123",
    "name": "Selling Prices"
  },
  "data": {
    "pp": [
      { "x": 0.0, "y": 0.0, "value": 200 },
      { "x": 0.5, "y": 0.5, "value": 200 },
      { "x": 1.25, "y": 1.5, "value": 450 }
    ]
  }
}
```

### Example Response: Percentage Mode

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListType": "diff",
  "priceList": {
    "id": "price_list_123",
    "name": "Selling Prices"
  },
  "data": {
    "pp": [
      { "x": 0.0, "y": 0.0, "value": 33.33 },
      { "x": 0.5, "y": 0.5, "value": 33.33 },
      { "x": 1.25, "y": 1.5, "value": 56.25 }
    ]
  }
}
```

### Possible Errors

* `400 Bad Request` - Missing or invalid cluster, priceListId1, priceListId2, or mode parameter
* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `404 Not Found` - One or both price lists not found or do not belong to the store
* `500 Internal Server Error` - Server error

<Info>
  This endpoint requires both price lists to be configured for the store. The system automatically fetches prices from both lists and calculates the difference. If either price list is missing or doesn't belong to the store, appropriate error responses are returned.
</Info>

***

## Set/Update Item Prices by Cluster and Axes

Set or update prices for multiple lens items in bulk. Uses database transactions and bulk operations for optimal performance. This endpoint supports **two different price formats** for maximum flexibility.

**Endpoint:** `POST /lens-pricing/items/prices`

### Request Body

The endpoint accepts two different price formats:

| Field         | Type                | Required | Description                                                         |
| ------------- | ------------------- | -------- | ------------------------------------------------------------------- |
| `cluster`     | `string`            | ✅ Yes    | The lens cluster, e.g., "1.56 HMC"                                  |
| `priceListId` | `string`            | ✅ Yes    | The price list ID to set prices in                                  |
| `signCombo`   | `string`            | ✅ Yes    | Sign combination: "pp", "nn", or "np"                               |
| `prices`      | `object` or `array` | ✅ Yes    | Price data in either Record format (object) or Array format (array) |

### Format 1: Record Format (Legacy - Still Supported)

The `prices` field is an object mapping `"sph|cyl"` keys to price values:

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListId": "price_list_123",
  "signCombo": "pp",
  "prices": {
    "0|0": 800,
    "0|0.25": 800,
    "0|1.5": 1250,
    "0.5|0.5": 800,
    "0.5|0.75": 800,
    "1.25|1.25": 800,
    "1.25|1.5": 1250
  }
}
```

**Field Descriptions:**

* `prices` (object) - Map of `"sph|cyl"` to price value
  * Key format: `"<sph>|<cyl>"` (e.g., `"0|0"`, `"0.25|0.5"`)
  * Value: Price as a number

<Note>
  Price keys use absolute values in the format `"sph|cyl"` (e.g., `"0.25|0.5"` means SPH=0.25, CYL=0.5). Values are numbers representing the price.
</Note>

### Format 2: Array Format (New - Recommended)

The `prices` field is an array of price point objects:

```json theme={null}
{
  "cluster": "1.56 HMC",
  "priceListId": "price_list_123",
  "signCombo": "pp",
  "prices": [
    { "x": 0.0,  "y": 0.0,  "value": 800 },
    { "x": 0.0,  "y": 0.25, "value": 800 },
    { "x": 0.0,  "y": 1.5,  "value": 1250 },
    { "x": 0.5,  "y": 0.5,  "value": 800 },
    { "x": 0.5,  "y": 0.75, "value": 800 },
    { "x": 1.25, "y": 1.25, "value": 800 },
    { "x": 1.25, "y": 1.5,  "value": 1250 }
  ]
}
```

**Field Descriptions:**

* `prices` (array) - Array of price point objects
  * `x` (number) - Sphere (SPH) value
  * `y` (number) - Cylinder (CYL) value
  * `value` (number) - Price value

<Info>
  The endpoint automatically normalizes both formats internally. Array format is converted to Record format before processing, ensuring both formats produce identical results. Empty arrays or empty objects are allowed (will result in 0 updates/inserts).
</Info>

### Success Response

```json theme={null}
{
  "success": true,
  "cluster": "1.56 HMC",
  "signCombo": "pp",
  "priceListId": "price_list_123",
  "updated": 3,
  "inserted": 2
}
```

### Response Fields

* `success` (boolean) - Indicates if the operation was successful
* `cluster` (string) - The cluster that was updated
* `signCombo` (string) - The sign combination that was updated
* `priceListId` (string) - ID of the price list that was modified
* `updated` (number) - Count of existing prices that were updated
* `inserted` (number) - Count of new prices that were inserted

### Example Requests

#### Example 1: Using Array Format (Recommended)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/lens-pricing/items/prices \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": [
        { "x": 0.0,  "y": 0.0,  "value": 800 },
        { "x": 0.0,  "y": 0.25, "value": 800 },
        { "x": 0.0,  "y": 1.5,  "value": 1250 },
        { "x": 0.5,  "y": 0.5,  "value": 800 },
        { "x": 0.5,  "y": 0.75, "value": 800 },
        { "x": 1.25, "y": 1.25, "value": 800 },
        { "x": 1.25, "y": 1.5,  "value": 1250 }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  const priceData = {
    cluster: "1.56 HMC",
    priceListId: "price_list_123",
    signCombo: "pp",
    prices: [
      { x: 0.0,  y: 0.0,  value: 800 },
      { x: 0.0,  y: 0.25, value: 800 },
      { x: 0.0,  y: 1.5,  value: 1250 },
      { x: 0.5,  y: 0.5,  value: 800 },
      { x: 0.5,  y: 0.75, value: 800 },
      { x: 1.25, y: 1.25, value: 800 },
      { x: 1.25, y: 1.5,  value: 1250 }
    ]
  }

  const response = await fetch("https://joptic.jethings.com/lens-pricing/items/prices", {
    method: "POST",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(priceData),
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/items/prices"

  price_data = {
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": [
          {"x": 0.0,  "y": 0.0,  "value": 800},
          {"x": 0.0,  "y": 0.25, "value": 800},
          {"x": 0.0,  "y": 1.5,  "value": 1250},
          {"x": 0.5,  "y": 0.5,  "value": 800},
          {"x": 0.5,  "y": 0.75, "value": 800},
          {"x": 1.25, "y": 1.25, "value": 800},
          {"x": 1.25, "y": 1.5,  "value": 1250}
      ]
  }

  response = requests.post(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>",
          "Content-Type": "application/json"
      },
      json=price_data
  )

  print(response.json())
  ```
</CodeGroup>

#### Example 2: Using Record Format (Legacy)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/lens-pricing/items/prices \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": {
        "0|0": 800,
        "0|0.25": 800,
        "0|1.5": 1250,
        "0.5|0.5": 800,
        "0.5|0.75": 800,
        "1.25|1.25": 800,
        "1.25|1.5": 1250
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const priceData = {
    cluster: "1.56 HMC",
    priceListId: "price_list_123",
    signCombo: "pp",
    prices: {
      "0|0": 800,
      "0|0.25": 800,
      "0|1.5": 1250,
      "0.5|0.5": 800,
      "0.5|0.75": 800,
      "1.25|1.25": 800,
      "1.25|1.5": 1250
    }
  }

  const response = await fetch("https://joptic.jethings.com/lens-pricing/items/prices", {
    method: "POST",
    headers: {
      "x-store-id": "your-store-id",
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(priceData),
  })

  const result = await response.json()
  ```

  ```python Python theme={null}
  import requests

  url = "https://joptic.jethings.com/lens-pricing/items/prices"

  price_data = {
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": {
          "0|0": 800,
          "0|0.25": 800,
          "0|1.5": 1250,
          "0.5|0.5": 800,
          "0.5|0.75": 800,
          "1.25|1.25": 800,
          "1.25|1.5": 1250
      }
  }

  response = requests.post(
      url,
      headers={
          "x-store-id": "your-store-id",
          "Authorization": "Bearer <token>",
          "Content-Type": "application/json"
      },
      json=price_data
  )

  print(response.json())
  ```
</CodeGroup>

<Info>
  This endpoint uses database transactions for atomicity and bulk operations for optimal performance. It's efficient for updating hundreds of prices at once with a single query to fetch existing prices and parallel updates using `Promise.all`. Prices are set per cluster and sign combination, and the endpoint automatically matches items based on their SPH/CYL values and signs.
</Info>

### Format Conversion

The endpoint automatically normalizes both formats internally:

* **Array Format** → Converted to Record format: `{x: 0.0, y: 0.0, value: 800}` → `{"0|0": 800}`
* **Record Format** → Passed through unchanged

The normalization happens in the controller, ensuring both formats produce identical results.

### Possible Errors

* `400 Bad Request` - Validation errors (invalid cluster, priceListId, signCombo, or request body structure)
* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `404 Not Found` - Price list not found or does not belong to the store, or LENS product type not found
* `500 Internal Server Error` - Server error

***

## Migration Guide

### For Frontend Teams

The endpoint now supports two price formats. Here's how to migrate:

**Old Format (Record) - Still Supported:**

```json theme={null}
{
  "prices": {
    "0|0": 800,
    "0.25|0.5": 850
  }
}
```

**New Format (Array) - Recommended:**

```json theme={null}
{
  "prices": [
    { "x": 0.0, "y": 0.0, "value": 800 },
    { "x": 0.25, "y": 0.5, "value": 850 }
  ]
}
```

<AccordionGroup>
  <Accordion title="Benefits of Array Format">
    * More intuitive structure (x, y coordinates)
    * Easier to iterate and manipulate
    * Better for form inputs and data grids
    * More readable in code
  </Accordion>

  <Accordion title="Backward Compatibility">
    * Both formats are fully supported
    * No breaking changes
    * Can migrate gradually
    * Record format will continue to work indefinitely
  </Accordion>
</AccordionGroup>

***

## Common Concepts

### Cluster Format

A cluster represents a lens type defined by:

<AccordionGroup>
  <Accordion title="Indice" icon="hash">
    **Indice** - The refractive index

    <br />

    Examples: "1.56", "1.50", "1.67"
  </Accordion>

  <Accordion title="Treatment" icon="sparkles">
    **Treatment** - The lens treatment

    <br />

    Examples: "HMC" (Hard Multi-Coating), "BB" (Blue Block), "HC" (Hard Coating)
  </Accordion>
</AccordionGroup>

**Example:** `"1.56 HMC"` means:

* Indice: 1.56
* Treatment: HMC (Hard Multi-Coating)
* Color is excluded from cluster names

### Item Name Format

Lens items follow this naming pattern:

```
{indice} [{color}] {treatment} {sph_sign}{sph_value} {cyl_sign}{cyl_value}
```

**Examples:**

* `"1.56 HMC +0.00 -0.00"` - No color, SPH=+0.00, CYL=-0.00
* `"1.50 PhGy BB +0.25 -0.50"` - Color=PhGy, SPH=+0.25, CYL=-0.50

### Sign Combinations

Lens prescriptions can have different sign combinations:

<CardGroup cols={3}>
  <Card title="pp" icon="plus">
    **pp** (+sph/+cyl): Both sphere and cylinder values are positive

    <br />

    Example items: "+0.00 +0.00", "+0.25 +0.50"
  </Card>

  <Card title="nn" icon="minus">
    **nn** (-sph/-cyl): Both sphere and cylinder values are negative

    <br />

    Example items: "-0.00 -0.00", "-1.00 -0.50"
  </Card>

  <Card title="np" icon="layers">
    **np** (-sph/+cyl): Sphere is negative, cylinder is positive

    <br />

    Example items: "-0.00 +0.00", "-0.50 +0.25"
  </Card>
</CardGroup>

### Price Key Format

In the table/matrix format, prices use keys in the format:

```
"{sph_absolute_value}|{cyl_absolute_value}"
```

**Examples:**

* `"0|0"` - SPH=0, CYL=0 (regardless of sign)
* `"0.25|0.5"` - SPH=0.25, CYL=0.5 (absolute values)
* `"1.25|1.5"` - SPH=1.25, CYL=1.5 (absolute values)

***

## Error Responses

All endpoints may return standard HTTP error codes:

* `400 Bad Request` - Invalid request parameters or body
* `401 Unauthorized` - Missing or invalid authentication token
* `403 Forbidden` - User does not have access to the store
* `404 Not Found` - Resource not found (price list, product type, etc.)
* `500 Internal Server Error` - Server error

### Error Response Format

```json theme={null}
{
  "statusCode": 404,
  "message": "No sell price list found for this store",
  "error": "Not Found"
}
```

***

## Usage Examples

### Example 1: Get all clusters for a store

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/clusters" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```
</CodeGroup>

### Example 2: Get first page of items for a cluster

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/items?cluster=1.56%20HMC&priceListId=price_list_123&page=1&limit=20" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```
</CodeGroup>

### Example 3: Get pricing table for a cluster

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://joptic.jethings.com/lens-pricing/items/table?cluster=1.56%20HMC&priceListId=price_list_123&format=matrix" \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>"
  ```
</CodeGroup>

### Example 4: Update prices using Array Format (Recommended)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/lens-pricing/items/prices \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": [
        { "x": 0.0, "y": 0.0, "value": 800 },
        { "x": 0.25, "y": 0.25, "value": 850 },
        { "x": 0.5, "y": 0.5, "value": 900 }
      ]
    }'
  ```
</CodeGroup>

### Example 5: Update prices using Record Format (Legacy)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://joptic.jethings.com/lens-pricing/items/prices \
    -H "x-store-id: your-store-id" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "cluster": "1.56 HMC",
      "priceListId": "price_list_123",
      "signCombo": "pp",
      "prices": {
        "0|0": 800,
        "0.25|0.25": 850,
        "0.5|0.5": 900
      }
    }'
  ```
</CodeGroup>

***

## Performance Considerations

<AccordionGroup>
  <Accordion title="Pagination">
    Always use pagination when fetching large lists of items to improve response times and reduce memory usage.
  </Accordion>

  <Accordion title="Bulk Updates">
    The `POST /items/prices` endpoint is optimized for bulk operations. Use it to update multiple prices in a single request rather than making individual requests.
  </Accordion>

  <Accordion title="Caching">
    Consider caching cluster lists as they change infrequently. This can significantly reduce API calls and improve performance.
  </Accordion>

  <Accordion title="Table Format">
    Use the `/items/table` endpoint for frontend table rendering. This format is optimized for matrix-based displays and reduces client-side processing.
  </Accordion>
</AccordionGroup>

***

## Version History

* **v1.0** - Initial implementation with clusters, paginated items, table format, and bulk price updates
* **v1.1** - Added Array format support for price updates (Record format still supported for backward compatibility)
* **v2.0** - Replaced `type` parameter with `priceListId` for all endpoints. Price list type is now derived from price list's `isBuying` and `isSelling` flags

***

<CardGroup cols={2}>
  <Card title="Price List API" icon="list" href="/j-optic/price-list-api">
    Create and manage price lists
  </Card>

  <Card title="Item API" icon="package" href="/j-optic/item-api">
    Create and manage optical items
  </Card>
</CardGroup>
