Products
Products represent the items available for sale in your application. On this page, we'll dive into the different product endpoints you can use to manage products programmatically. We'll look at how to query, create, update, and delete products.
The product model
The product model includes detailed information about the product, including its variants, prices, and inventory details.
Properties
- Name
id- Type
- string
- Description
Unique identifier for the product.
- Name
name- Type
- string
- Description
The name of the product.
- Name
description- Type
- string
- Description
The description of the product.
- Name
imageUrl- Type
- string
- Description
URL of the product image.
- Name
price- Type
- number
- Description
The base price of the product.
- Name
discountPrice- Type
- number
- Description
The lowest price of the product.
- Name
currency- Type
- string
- Description
The currency code of the product price.
- Name
variants- Type
- array
- Description
List of product variants.
- Name
id- Type
- string
- Description
Unique identifier for the product variant.
- Name
name- Type
- string
- Description
The name of the variant.
- Name
inventoryQuantity- Type
- number
- Description
The inventory quantity of the variant.
- Name
inventoryTotal- Type
- number
- Description
The total inventory quantity of the variant in grams
- Name
price- Type
- number
- Description
The base price of the variant.
- Name
discountPrice- Type
- number
- Description
The discounted price of the variant.
- Name
discountPercentage- Type
- number
- Description
The discount percentage of the variant.
- Name
currencyCode- Type
- string
- Description
The currency code of the variant price.
- Name
priceListId- Type
- string
- Description
The price list identifier of the variant.
- Name
priceListName- Type
- string
- Description
The name of the price list.
- Name
weight- Type
- number
- Description
The weight of the variant.
- Name
prices- Type
- array
- Description
List of prices for the variant.
- Name
id- Type
- string
- Description
Unique identifier for the price.
- Name
price- Type
- number
- Description
The price amount.
- Name
priceList- Type
- object
- Description
The price list details.
- Name
id- Type
- string
- Description
Unique identifier for the price list.
- Name
name- Type
- string
- Description
The name of the price list.
- Name
currency- Type
- string
- Description
The currency code of the price.
- Name
createdAt- Type
- string
- Description
The timestamp when the price was created.
- Name
quantity- Type
- number
- Description
The quantity of the variant (grams in the fraction).
- Name
strain- Type
- object
- Description
The strain information of the product if it is flower
- Name
cannabinoids- Type
- object
- Description
The cannabinoids content of the strain.
- Name
thc- Type
- number
- Description
The THC content percentage of the strain.
- Name
cbg- Type
- number
- Description
The CBG content percentage of the strain.
- Name
cbd- Type
- number
- Description
The CBD content percentage of the strain.
- Name
terpenes- Type
- array
- Description
List of terpenes present in the strain.
- Name
flavors- Type
- array
- Description
List of flavors associated with the strain.
- Name
genetics- Type
- object
- Description
The genetics of the strain.
- Name
parents- Type
- array
- Description
List of parent strains.
- Name
children- Type
- array
- Description
List of child strains.
- Name
category- Type
- string
- Description
The category of the product.
- Name
isFlower- Type
- boolean
- Description
Whether the product is a flower.
- Name
inventoryQuantity- Type
- number
- Description
The total inventory quantity of the product.
Complete product example
{
"id": "prod_01HQ8BLUE_DREAM",
"name": "Blue Dream Indoor",
"description": "Blue Dream es una cepa híbrida sativa-dominante que cruza Blueberry con Haze. Conocida por su equilibrio entre relajación cerebral y estimulación corporal, es ideal para aliviar dolor, depresión y náuseas mientras mantiene claridad mental.",
"imageUrl": "https://storage.cannahub.tech/products/blue-dream-main.jpg",
"price": 330000,
"discountPrice": 320000,
"currency": "ARS",
"variants": [
{
"id": "variant_01HQ_BD_1G",
"name": "Blue Dream 1g",
"inventoryQuantity": 20,
"inventoryTotal": 20,
"weight": 1,
"price": 350000,
"discountPrice": null,
"discountPercentage": 0,
"currencyCode": "ARS",
"priceListId": "plist_basic",
"priceListName": "Básica",
"quantity": 1,
"prices": [
{
"id": "price_01HQ_BD_1G_BASIC",
"price": 350000,
"priceList": {
"id": "plist_basic",
"name": "Básica"
},
"currency": "ARS",
"createdAt": "2024-03-15T10:00:00Z"
},
{
"id": "price_01HQ_BD_1G_PREMIUM",
"price": 332500,
"priceList": {
"id": "plist_premium",
"name": "Premium"
},
"currency": "ARS",
"createdAt": "2024-03-15T10:00:00Z"
}
]
},
{
"id": "variant_01HQ_BD_5G",
"name": "Blue Dream 5g",
"inventoryQuantity": 8,
"inventoryTotal": 40,
"weight": 5,
"price": 1650000,
"discountPrice": 1650000,
"discountPercentage": 6,
"currencyCode": "ARS",
"priceListId": "plist_basic",
"priceListName": "Básica",
"quantity": 5,
"prices": [
{
"id": "price_01HQ_BD_5G_BASIC",
"price": 1650000,
"priceList": {
"id": "plist_basic",
"name": "Básica"
},
"currency": "ARS",
"createdAt": "2024-03-15T10:00:00Z"
}
]
}
],
"strain": {
"id": "strain_blue_dream",
"name": "Blue Dream",
"type": "hybrid",
"cannabinoids": {
"thc": 20,
"cbg": 0.8,
"cbd": 1.5
},
"terpenes": ["Mirceno", "Pineno", "Caryofileno", "Limoneno"],
"flavors": ["Arándano", "Dulce", "Terroso"],
"genetics": {
"parents": ["Blueberry", "Haze"],
"children": []
},
"effects": {
"helpsWith": ["Dolor", "Depresión", "Náuseas"],
"feelings": ["Eufórico", "Creativo", "Relajado"],
"negatives": ["Boca seca", "Ojos rojos"]
}
},
"category": "flores",
"isFlower": true,
"manageInventory": true,
"inventoryQuantity": 60
}
Field Mapping Reference
Cannahub uses Medusa.js for product management. Here's how frontend types map to Medusa API:
Product ↔ Medusa Product Mapping
| Cannahub Frontend | Medusa API | Type | Notes |
|---|---|---|---|
id | product.id | string | Medusa product ID |
name | product.title | string | - |
description | product.description | string | Full description |
imageUrl | product.thumbnail | string (URL) | Main product image |
images | product.images[] | array | All product images |
price | Calculated from variants | number | Lowest variant price |
discountPrice | Calculated from variants | number | Lowest discount price |
currency | region.currency_code | string | From region (ARS, USD, etc.) |
variants | product.variants[] | array | Product fractions |
strain | product.metadata.strain | object | Complete strain info in metadata |
category | product.metadata.category | string | flores, extracciones, etc. |
isFlower | product.metadata.isFlower | boolean | Whether product is flower |
manageInventory | variant.manage_inventory | boolean | Track stock or not |
inventoryQuantity | Sum of variant quantities | number | Total units across all variants |
Variant ↔ Medusa Variant Mapping
| Cannahub Frontend | Medusa API | Type | Notes |
|---|---|---|---|
id | variant.id | string | - |
name | variant.title | string | e.g. "Blue Dream 5g" |
inventoryQuantity | variant.inventory_quantity | number | Units available |
inventoryTotal | variant.metadata.inventoryTotal | number | Total grams (quantity × weight) |
weight | variant.metadata.weight | number | Grams per unit |
price | variant.prices[].amount | number | Price for current price list |
discountPrice | Calculated | number | If discount applied |
discountPercentage | variant.metadata.discount_percentage | number | Discount % for this fraction |
quantity | variant.metadata.quantity | number | Same as weight (for clarity) |
prices[] | variant.prices[] | array | All prices across price lists |
Complete Product Metadata Structure
{
"metadata": {
"strainId": "strain_blue_dream",
"isFlower": true,
"requireReprocann": true,
"category": "flores",
"subcategory": "hibridas",
"thc": 20,
"cbd": 1.5,
"cbg": 0.8,
"terpenes": ["mirceno", "pineno", "caryofileno", "limoneno"],
"effects": ["energetic", "creative", "euphoric"],
"cultivationType": "indoor",
"harvestDate": "2024-03-01",
"batchNumber": "BATCH-2024-03-001",
"strain": {
"id": "strain_blue_dream",
"name": "Blue Dream",
"altNames": "Azure Haze, BD",
"type": "hybrid",
"description": "Cepa híbrida equilibrada...",
"cannabinoids": {
"thc": 20,
"cbd": 1.5,
"cbg": 0.8
},
"terpenes": ["Mirceno", "Pineno", "Caryofileno"],
"flavors": ["Arándano", "Dulce", "Terroso"],
"genetics": {
"parents": ["Blueberry", "Haze"],
"children": []
},
"effects": {
"helpsWith": ["Dolor", "Depresión", "Náuseas"],
"feelings": ["Eufórico", "Creativo", "Relajado"],
"negatives": ["Boca seca", "Ojos rojos"]
},
"growInformation": {
"type": "indoor",
"difficulty": "moderate",
"height": "medium",
"yield": "high",
"floweringWeeks": "9-10",
"notes": "Requiere podas regulares"
}
}
}
}
Variant Metadata Structure
{
"metadata": {
"weight": 5,
"unit": "grams",
"inventoryTotal": 40,
"quantity": 5,
"packaging": "jar",
"discount_percentage": 6
}
}
List all products
This endpoint allows you to retrieve a paginated list of all your products. By default, a maximum of ten products are shown per page.
Optional attributes
- Name
category- Type
- string
- Description
Filter products by category.
- Name
limit- Type
- integer
- Description
Limit the number of products returned.
- Name
page- Type
- integer
- Description
Start offset of products returned.
- Name
sort- Type
- string
- Description
Sort the list of products by specified attribute.
- Name
direction- Type
- string
- Description
Sort direction, can be 'asc' or 'desc'.
- Name
search- Type
- string
- Description
Search products by name or description.
Request
curl -G https://api.yourapp.com/store/products \
-H "Authorization: Bearer {token}" \
-d limit=10
Response
[
{
"id": "l7cGNIBKZiNJ6wqF",
"name": "Product 1",
"description": "This is product 1",
"imageUrl": "https://assets.yourapp.com/products/product1.jpg",
"price": 100,
"discountPrice": 80,
"currency": "USD",
"variants": [
{
"id": "variant1",
"name": "Variant 1",
"inventoryQuantity": 50,
"price": 100,
"currencyCode": "USD",
// ...
}
],
"strain": null,
"category": "Category 1",
"isFlower": true,
"inventoryQuantity": 50
}
]
Retrieve a product
This endpoint allows you to retrieve a product by providing the product ID.
Request
curl https://yourapp.com/api/products/:id \
-H "Authorization: Bearer {token}"
Response
{
"id": "l7cGNIBKZiNJ6wqF",
"name": "Product 1",
"description": "This is product 1",
"imageUrl": "https://assets.yourapp.com/products/product1.jpg",
"price": 100,
"discountPrice": 80,
"currency": "USD",
"variants": [
{
"id": "variant1",
"name": "Variant 1",
"inventoryQuantity": 50,
"price": 100,
"currencyCode": "USD"
}
],
"strain": null,
"category": "Category 1",
"isFlower": true,
"inventoryQuantity": 50
}
Create a product
This endpoint allows you to create a new product in your application.
Medusa Reference
Required attributes
- Name
name- Type
- string
- Description
The name of the product.
- Name
description- Type
- string
- Description
The description of the product.
- Name
imageUrl- Type
- string
- Description
URL of the product image.
- Name
category- Type
- string
- Description
The category of the product.
Optional attributes
- Name
strain- Type
- object
- Description
The strain information of the product.
Request
curl -X POST https://yourapp.com/api/products \
-H "Authorization: Bearer {token}" \
-d '{
"name": "Product 1",
"description": "This is product 1",
"imageUrl": "https://assets.yourapp.com/products/product1.jpg",
"currency": "USD"
}'
Response
{
"id": "l7cGNIBKZiNJ6wqF",
"name": "Product 1",
"description": "This is product 1",
"imageUrl": "https://assets.yourapp.com/products/product1.jpg",
"currency": "USD",
"variants": [],
"strain": null,
"category": "Category 1",
"isFlower": true,
}
Update a product
This endpoint allows you to update an existing product in your application.
Medusa Reference
Optional attributes
- Name
name- Type
- string
- Description
The name of the product.
- Name
description- Type
- string
- Description
The description of the product.
- Name
imageUrl- Type
- string
- Description
URL of the product image.
- Name
category- Type
- string
- Description
The category of the product.
- Name
strain- Type
- object
- Description
The strain information of the product.
Request
curl -X PUT https://yourapp.com/api/products/:id \
-H "Authorization: Bearer {token}" \
-d '{
"name": "Updated Product",
"description": "This is an updated product",
"imageUrl": "https://assets.yourapp.com/products/updatedproduct.jpg",
}'
Response
{
"id": "l7cGNIBKZiNJ6wqF",
"name": "Updated Product",
"description": "This is an updated product",
"imageUrl": "https://assets.yourapp.com/products/updatedproduct.jpg",
"currency": "USD",
"variants": [],
"strain": null,
"category": "Category 1",
"isFlower": true,
}
Create a product variant
This endpoint allows you to create a new product variant in your application.
Medusa Reference
Required attributes
- Name
name- Type
- string
- Description
The name of the variant.
- Name
inventoryQuantity- Type
- number
- Description
The inventory quantity of the variant.
- Name
currencyCode- Type
- string
- Description
The currency code of the variant price.
Optional attributes
- Name
weight- Type
- number
- Description
The weight of the variant.
Request
curl -X POST https://yourapp.com/api/products/:productId/variants \
-H "Authorization: Bearer {token}" \
-d '{
"name": "New Variant",
"inventoryQuantity": 50,
"currencyCode": "USD"
}'
Response
{
"id": "variantId",
"name": "New Variant",
"inventoryQuantity": 50,
"currencyCode": "USD",
"weight": 10
}
BFF Route: Product with Inventory
Esta ruta BFF resuelve el problema N+1 al obtener un producto con todos sus datos de inventario. En lugar de hacer múltiples llamadas (1 para el producto + N para cada variante), esta ruta agrega toda la información en una sola llamada.
Problema que resuelve
Flujo actual (N+1 calls):
1. GET /admin/products/:id → Producto base
2. GET /admin/stock-locations → Ubicaciones disponibles
3. GET /admin/inventory-items?variant_id=v1 → Inventario variante 1
4. GET /admin/inventory-items?variant_id=v2 → Inventario variante 2
5. GET /admin/inventory-items?variant_id=v3 → Inventario variante 3
...N más por cada variante
Ejemplo: Producto con 5 variantes = 7+ llamadas API
Request
- Name
id- Type
- string
- Description
El ID del producto.
- Name
locationId- Type
- string
- Description
Filtrar inventario por ubicación específica. Si no se proporciona, retorna inventario de todas las ubicaciones.
Response
- Name
product- Type
- Product
- Description
El producto completo con strain, categoría y metadatos.
- Name
inventory- Type
- object
- Description
Mapa de inventario por variante.
- Name
[variantId]- Type
- VariantInventory
- Description
Datos de inventario para cada variante.
- Name
quantity- Type
- number
- Description
Cantidad total de unidades.
- Name
available- Type
- number
- Description
Cantidad disponible para venta.
- Name
reserved- Type
- number
- Description
Cantidad reservada en carritos/pedidos.
- Name
totalGrams- Type
- number
- Description
Total en gramos (quantity × weight).
- Name
byLocation- Type
- array
- Description
Desglose por ubicación.
- Name
locationId- Type
- string
- Description
ID de la ubicación.
- Name
locationName- Type
- string
- Description
Nombre de la ubicación.
- Name
quantity- Type
- number
- Description
Cantidad en esta ubicación.
- Name
available- Type
- number
- Description
Disponible en esta ubicación.
- Name
totalInventoryGrams- Type
- number
- Description
Total de gramos en inventario sumando todas las variantes.
- Name
totalInventoryUnits- Type
- number
- Description
Total de unidades en inventario.
- Name
lowStockVariants- Type
- array
- Description
Lista de variantes con stock bajo (menos de 5 unidades).
Request
curl https://api.cannahub.tech/api/products/prod_01HQ8BLUE_DREAM/with-inventory \
-H "Authorization: Bearer {token}"
Response
{
"product": {
"id": "prod_01HQ8BLUE_DREAM",
"name": "Blue Dream Indoor",
"description": "Cepa híbrida sativa-dominante...",
"imageUrl": "https://storage.cannahub.tech/products/blue-dream.jpg",
"price": 330000,
"currency": "ARS",
"variants": [
{
"id": "variant_01HQ_BD_1G",
"name": "Blue Dream 1g",
"weight": 1,
"price": 350000
},
{
"id": "variant_01HQ_BD_5G",
"name": "Blue Dream 5g",
"weight": 5,
"price": 1650000
}
],
"strain": {
"name": "Blue Dream",
"type": "hybrid",
"cannabinoids": { "thc": 20, "cbd": 1.5 }
},
"isFlower": true
},
"inventory": {
"variant_01HQ_BD_1G": {
"quantity": 20,
"available": 18,
"reserved": 2,
"totalGrams": 20,
"byLocation": [
{
"locationId": "sloc_dispensary",
"locationName": "Dispensario Principal",
"quantity": 15,
"available": 13
},
{
"locationId": "sloc_warehouse",
"locationName": "Depósito",
"quantity": 5,
"available": 5
}
]
},
"variant_01HQ_BD_5G": {
"quantity": 8,
"available": 8,
"reserved": 0,
"totalGrams": 40,
"byLocation": [
{
"locationId": "sloc_dispensary",
"locationName": "Dispensario Principal",
"quantity": 8,
"available": 8
}
]
}
},
"totalInventoryGrams": 60,
"totalInventoryUnits": 28,
"lowStockVariants": []
}
Flujo interno del BFF
Comparación de rendimiento
| Métrica | Sin BFF | Con BFF | Mejora |
|---|---|---|---|
| Llamadas API (5 variantes) | 7+ | 1 | -86% |
| Latencia típica | ~700ms | ~200ms | -71% |
| Cálculo de gramos | Cliente | Servidor | Centralizado |
| Detección stock bajo | Manual | Automático | Incluido |
BFF Route: Products with Inventory (List)
Versión paginada que lista productos con su inventario agregado. Ideal para dashboards de administración y vistas de catálogo con stock.
Query Parameters
- Name
locationId- Type
- string
- Description
Filtrar por ubicación específica.
- Name
limit- Type
- number
- Description
Número de productos por página (default: 20).
- Name
offset- Type
- number
- Description
Offset para paginación.
- Name
category- Type
- string
- Description
Filtrar por categoría.
- Name
lowStock- Type
- boolean
- Description
Si es
true, solo retorna productos con stock bajo.
- Name
outOfStock- Type
- boolean
- Description
Si es
true, solo retorna productos sin stock.
Request
curl "https://api.cannahub.tech/api/products/with-inventory?limit=20&lowStock=true" \
-H "Authorization: Bearer {token}"
Response
{
"products": [
{
"id": "prod_01HQ8BLUE_DREAM",
"name": "Blue Dream Indoor",
"imageUrl": "https://storage.cannahub.tech/...",
"totalInventoryGrams": 60,
"totalInventoryUnits": 28,
"variants": [
{
"id": "variant_01HQ_BD_1G",
"name": "1g",
"quantity": 20,
"available": 18
}
],
"isLowStock": false,
"isOutOfStock": false
}
],
"count": 45,
"limit": 20,
"offset": 0,
"summary": {
"totalProducts": 45,
"lowStockCount": 5,
"outOfStockCount": 2,
"totalInventoryGrams": 15000
}
}
Update a product variant
This endpoint allows you to update an existing product variant in your application.
Medusa Reference
Optional attributes
- Name
name- Type
- string
- Description
The name of the variant.
- Name
inventoryQuantity- Type
- number
- Description
The inventory quantity of the variant.
- Name
weight- Type
- number
- Description
The weight of the variant.
Request
curl -X PUT https://yourapp.com/api/products/:productId/variants/:variantId \
-H "Authorization: Bearer {token}" \
-d '{
"name": "Updated Variant",
"inventoryQuantity": 100,
"weight": 10
}'
Response
{
"id": "variantId",
"name": "Updated Variant",
"inventoryQuantity": 100,
"weight": 10,
"price": 100,
"discountPrice": 80,
"currencyCode": "USD",
"priceListId": "priceListId",
"priceListName": "Price List",
"prices": [
{
"id": "priceId",
"price": 100,
"priceList": {
"id": "priceListId",
"name": "Price List"
},
"currency": "USD",
"createdAt": "2023-05-13T12:00:00Z"
},
{
"id": "priceId",
"price": 80,
"priceList": {
"id": "priceListId",
"name": "Price List"
},
"currency": "USD",
"createdAt": "2023-05-14T12:00:00Z"
}
]
}