Skip to main content
All endpoints require JWT authentication via Authorization: Bearer <token> header.

Get All Products

GET /api/admin/inventory

Fetch all products with inventory data

Authentication

Required. Validated via verifyAdminToken(req) helper from auth-helper.js:3-18.

Response

Returns an array of product objects ordered by name and variant.
products
array

Implementation

From inventory.js:14-20:
const products = await prisma.product.findMany({
  orderBy: [
    { name: 'asc' },
    { variantName: 'asc' } // Ensure variants group nicely
  ]
});
return res.status(200).json(products);

Example Response

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "sku": "KAIU-LAV-100",
    "name": "Jabón Natural de Lavanda",
    "slug": "jabon-natural-de-lavanda-kaiu-lav-100",
    "description": "Jabón artesanal con aceites esenciales de lavanda",
    "variantName": "100g",
    "category": "Jabones",
    "price": 29900,
    "stock": 45,
    "isActive": true,
    "images": [
      "https://cdn.kaiucol.com/products/jabon-lavanda-100g.jpg"
    ],
    "benefits": "Hidratante, relajante, aroma natural",
    "weight": 0.15,
    "width": 8,
    "height": 3,
    "length": 12,
    "createdAt": "2026-01-15T10:00:00Z",
    "updatedAt": "2026-03-01T14:30:00Z"
  }
]

Create Product

POST /api/admin/inventory

Add a new product to the catalog

Request Body

name
string
required
Product name
price
number
required
Price in COP (Colombian Pesos)
sku
string
Unique SKU. Auto-generated if not provided: KAIU-{timestamp}
description
string
Product description (default: empty string)
variantName
string
Variant identifier (e.g., “100g”, “Verde”, “Talla M”)
category
string
Product category (default: “Otros”)
stock
number
Initial stock quantity (default: 0)
isActive
boolean
Product active status (default: true)
images
array
Array of image URLs (default: empty array)
benefits
string
Product benefits text
weight
number
Weight in kg (default: 0.2)
width
number
Width in cm (default: 10)
height
number
Height in cm (default: 10)
length
number
Length in cm (default: 10)

Implementation Details

From inventory.js:36-37:
const safeSku = sku || `KAIU-${Date.now().toString(36).toUpperCase()}`;
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '-' + safeSku.toLowerCase();
Auto-generated Fields:
  • sku: Generated from timestamp if not provided
  • slug: Created from name + SKU for SEO-friendly URLs

Example Request

const response = await fetch('https://api.kaiucol.com/api/admin/inventory', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  },
  body: JSON.stringify({
    name: 'Shampoo Sólido de Romero',
    description: 'Shampoo sólido con extracto de romero para cabello graso',
    variantName: '80g',
    category: 'Cuidado Capilar',
    price: 34900,
    stock: 30,
    isActive: true,
    weight: 0.08,
    images: ['https://cdn.kaiucol.com/products/shampoo-romero.jpg'],
    benefits: 'Controla la grasa, estimula el crecimiento'
  })
});

Example Response

{
  "success": true,
  "product": {
    "id": "660e8400-e29b-41d4-a716-446655440001",
    "sku": "KAIU-LXYZ1234",
    "name": "Shampoo Sólido de Romero",
    "slug": "shampoo-solido-de-romero-kaiu-lxyz1234",
    "description": "Shampoo sólido con extracto de romero para cabello graso",
    "variantName": "80g",
    "category": "Cuidado Capilar",
    "price": 34900,
    "stock": 30,
    "isActive": true,
    "weight": 0.08,
    "createdAt": "2026-03-04T15:20:00Z",
    "updatedAt": "2026-03-04T15:20:00Z"
  }
}

Update Product

PUT /api/admin/inventory

Update existing product properties

Request Body

sku
string
required
SKU of product to update
updates
object
required
Object containing fields to update. Only provided fields will be modified.

Implementation

From inventory.js:76-91:
// Allowed fields to update
const { price, stock, isActive, name, description, category, 
        variantName, benefits, weight, width, height, length, images } = updates;

const dataToUpdate = {};
if (typeof price !== 'undefined') dataToUpdate.price = Number(price);
if (typeof stock !== 'undefined') dataToUpdate.stock = Number(stock);
if (typeof isActive !== 'undefined') dataToUpdate.isActive = Boolean(isActive);
// ... (other fields)

const updatedProduct = await prisma.product.update({
  where: { sku: sku },
  data: dataToUpdate
});
Partial Updates: Only fields included in the updates object are modified. Omitted fields remain unchanged.

Example Request

// Update stock and price
await fetch('https://api.kaiucol.com/api/admin/inventory', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <token>'
  },
  body: JSON.stringify({
    sku: 'KAIU-LAV-100',
    updates: {
      stock: 60,
      price: 32900,
      isActive: true
    }
  })
});

Example Response

{
  "success": true,
  "product": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "sku": "KAIU-LAV-100",
    "name": "Jabón Natural de Lavanda",
    "price": 32900,
    "stock": 60,
    "isActive": true,
    "updatedAt": "2026-03-04T16:45:00Z"
  }
}

Common Use Cases

Bulk Stock Update

const products = [
  { sku: 'KAIU-LAV-100', stock: 50 },
  { sku: 'KAIU-ROM-80', stock: 30 },
  { sku: 'KAIU-MAN-120', stock: 25 }
];

for (const product of products) {
  await fetch('https://api.kaiucol.com/api/admin/inventory', {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      sku: product.sku,
      updates: { stock: product.stock }
    })
  });
}

Create Product Variant

// Create new variant of existing product
await fetch('https://api.kaiucol.com/api/admin/inventory', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Jabón Natural de Lavanda',
    variantName: '200g', // New size
    category: 'Jabones',
    price: 49900,
    stock: 20,
    weight: 0.2,
    images: ['https://cdn.kaiucol.com/products/jabon-lavanda-200g.jpg']
  })
});
Products with the same name but different variantName are displayed together in the admin interface and grouped by the orderBy clause.

Build docs developers (and LLMs) love