Products
Products API
Endpoints for managing your product catalog, including brands and categories for organization.
List Products
GET /api/products
Permission: products:read
Retrieve a paginated list of active products for the current tenant.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| search | string | No | Search by product name or model/SKU (case-insensitive) |
| brandId | string | No | Filter by brand ID |
| categoryId | string | No | Filter by category ID |
| page | number | No | Page number (default: 1) |
Response 200 OK:
{
"data": [
{
"id": "clxyz...",
"name": "Laptop HP ProBook 450",
"description": "Laptop empresarial con procesador i7",
"modelSku": "PB450-G10",
"price": "18500.00",
"isActive": true,
"brand": { "id": "...", "name": "HP" },
"category": { "id": "...", "name": "Laptops" },
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"total": 120,
"page": 1,
"totalPages": 6
}cURL Example:
curl -X GET "https://cotizera.com/api/products?search=laptop&brandId=clxyz&page=1" \
-H "Cookie: next-auth.session-token=YOUR_TOKEN"Create Product
POST /api/products
Permission: products:create
Create a new product. If modelSku is provided, it must be unique within the tenant.
Request Body:
{
"name": "Monitor Dell UltraSharp 27\"",
"description": "Monitor 4K IPS con USB-C",
"modelSku": "U2723QE",
"price": 12500,
"brandId": "clxyz...",
"categoryId": "clxyz..."
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Product name (min 1 character) |
| description | string | No | Product description |
| modelSku | string | No | Model number or SKU (unique per tenant if provided) |
| price | number | Yes | Unit price (> 0) |
| brandId | string | No | Brand ID |
| categoryId | string | No | Category ID |
Response 201 Created: Returns the created product with brand and category included.
Errors:
| Status | Error | When |
|---|---|---|
| 400 | Zod validation message | Invalid input |
| 403 | "Sin permisos" | Role lacks products:create |
| 409 | "Ya existe un producto con ese modelo/SKU" | Duplicate SKU in tenant |
Side Effects:
- Fires
product.createdwebhook event - Triggers onboarding step
first_productcompletion - Logs audit entry with action
create
Get Single Product
GET /api/products/:id
Permission: products:read
Retrieve a single active product with brand and category details.
Response 200 OK: Returns the product object with brand and category included.
Errors:
| Status | Error | When |
|---|---|---|
| 404 | "Producto no encontrado" | Product not found, inactive, or wrong tenant |
Update Product
PUT /api/products/:id
Permission: products:update
Update an existing product. If modelSku is changed, uniqueness is re-validated.
Request Body: Same schema as Create Product.
Response 200 OK: Returns the updated product with brand and category.
Errors:
| Status | Error | When |
|---|---|---|
| 404 | "Producto no encontrado" | Product not found or inactive |
| 409 | "Ya existe un producto con ese modelo/SKU" | New SKU conflicts with existing product |
Delete Product (Soft Delete)
DELETE /api/products/:id
Permission: products:delete
Soft-deletes a product by setting isActive to false.
Response 200 OK:
{
"success": true
}Brands
GET /api/brands
List all active brands for the current tenant, sorted alphabetically.
Response 200 OK:
[
{ "id": "clxyz...", "name": "HP", "tenantId": "...", "isActive": true },
{ "id": "clxyz...", "name": "Dell", "tenantId": "...", "isActive": true }
]POST /api/brands
Create a new brand. Name must be unique within the tenant.
Request Body:
{
"name": "Lenovo"
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Brand name (min 1 character) |
Response 201 Created: Returns the created brand.
Errors:
| Status | Error | When |
|---|---|---|
| 409 | "Ya existe una marca con ese nombre" | Duplicate brand name in tenant |
Categories
GET /api/categories
List all active categories for the current tenant, sorted alphabetically.
Response 200 OK:
[
{ "id": "clxyz...", "name": "Laptops", "tenantId": "...", "isActive": true },
{ "id": "clxyz...", "name": "Monitores", "tenantId": "...", "isActive": true }
]POST /api/categories
Create a new category. Name must be unique within the tenant.
Request Body:
{
"name": "Impresoras"
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Category name (min 1 character) |
Response 201 Created: Returns the created category.
Errors:
| Status | Error | When |
|---|---|---|
| 409 | "Ya existe una categoria con ese nombre" | Duplicate category name in tenant |