Documentación
API ez-catalog
REST + JSON. Todas las rutas viven bajo /v1.
Base URL: https://api.ez-catalog.huggian.com
Quick start
- Registrate en huggian.com/catalog/signup. El plan Free no pide tarjeta.
- Guardá la API key que aparece en pantalla. No se muestra de nuevo. También llega por email.
- Mandala en el header
X-Api-Keyen cada request.
cURL
curl https://api.ez-catalog.huggian.com/v1/products/barcode/7790895000997 \
-H "X-Api-Key: ezc_free_xxxxxxxx" Autenticación
Toda request a /v1/* requiere el header X-Api-Key. La ruta /health es pública. Las imágenes se sirven desde images.huggian.com sin autenticación.
401 Unauthorized: falta la key o es inválida.403 Forbidden: key válida pero sin permiso (plan insuficiente o scope faltante).429 Too Many Requests: rate limit excedido. Esperá el próximo minuto o miráRetry-After.
Planes y límites
| Plan | Rate limit | Cuota mensual | Lectura | Escritura | Full-text |
|---|---|---|---|---|---|
| Free | 10 rpm | 50 | Barcode, marca, familia, categoría | Propuestas | No |
| Basic | 60 rpm | 25.000 | Todo | Productos e imágenes | Sí |
| Pro | 300 rpm | 200.000 | Todo | Todo, incluido borrado | Sí |
El rate limit es por API key, por minuto, con ventana deslizante. La cuota mensual se reinicia el día 1 a las 00:00 UTC y no se resetea al cambiar de plan.
Errores
Todos los errores responden JSON con el campo error. Algunos agregan message con una pista.
{
"error": "full_text_search_not_available",
"message": "Full-text search requires a Basic or Pro plan. Use /v1/products/barcode/:ean for barcode lookup."
} 400: body inválido.401: auth faltante o inválida.403: plan insuficiente.404: el recurso no existe.409: conflicto (barcode o marca duplicada).429: rate limit.500: interno. Reintentá con backoff.
Productos
/v1/products/barcode/{ean} Free+ Buscar producto por código de barras EAN.
Response
{
"id": "8a3e...",
"barcode": "7790895000997",
"barcode_type": "EAN13",
"name": "Yerba Mate Taragüi 500g",
"brand": "Taragüi",
"brand_id": "f1c2...",
"brand_logo_url": "https://images.huggian.com/brands/taragui.png",
"image_url": "https://images.huggian.com/products/7790895000997.jpg",
"category_id": "ab12...",
"country_of_origin": "AR",
"verified": true,
"product_type": "good",
"presentation": "500g",
"unit_of_measure": "g",
"ncm_code": "0903.00.10",
"afip_category": null,
"family_id": "c9d0...",
"attributes": null,
"source": "manual",
"created_at": "2026-01-12T10:00:00Z",
"updated_at": "2026-04-30T09:00:00Z"
} - Devuelve 404 si el EAN no está en el catálogo.
/v1/products/{id} Free+ Buscar producto por UUID.
Response
Mismo shape que /barcode/{ean}. /v1/products/search?q=&brand=&family=&category=&page=&per_page= Basic+ Búsqueda full-text con filtros y paginación.
Response
{
"items": [ /* Product[] */ ],
"total": 142,
"page": 1,
"per_page": 20
} - page por defecto 1 (mín 1). per_page por defecto 20 (1..100).
- brand, family, category aceptan UUID.
- Free recibe 403 con error full_text_search_not_available.
/v1/products Basic+ Crear producto.
Request body
{
"barcode": "7790895000997",
"barcode_type": "EAN13",
"name": "Yerba Mate Taragüi 500g",
"description": null,
"category_id": "ab12...",
"brand": "Taragüi",
"brand_id": null,
"image_url": null,
"ncm_code": "0903.00.10",
"afip_category": null,
"country_of_origin": "AR",
"source": "manual",
"presentation": "500g",
"subcategory": null,
"unit_of_measure": "g",
"product_type": "good",
"attributes": null
} Response
201 Created → Product completo. - Solo `name` es obligatorio.
- 409 barcode already exists si el EAN choca con otro producto.
- 409 brand name already exists si la marca textual ya existe normalizada.
/v1/products/{id} Basic+ Actualizar producto (todos los campos opcionales, parche).
Request body
{ "name": "Nuevo nombre", "verified": true } Response
200 → Product actualizado. /v1/products/{id} Basic+ Borrar producto.
Response
204 No Content. Marcas
/v1/brands Free+ Listar marcas.
Response
[
{ "id": "f1c2...", "name": "Taragüi", "slug": "taragui", "logo_url": "...", "created_at": "..." }
] /v1/brands/{id} Free+ Obtener marca por UUID.
Response
Brand. /v1/brands Basic+ Crear marca (normaliza acentos, sufijos corporativos, mayúsculas).
Request body
{ "name": "La Serenísima", "logo_url": null } Response
201 → Brand. 409 si la marca normalizada ya existe. Familias
/v1/families Free+ Listar familias de producto (variantes de un mismo SKU base).
Response
ProductFamily[]. /v1/families/{id} Free+ Obtener familia por UUID.
Response
ProductFamily. Categorías
/v1/categories Free+ Listar categorías (árbol jerárquico, `parent_id` apunta al padre o `null`).
Response
Category[]. Imágenes
/v1/products/{ean}/images Free+ Listar imágenes de un producto por EAN.
Response
{ "images": [ { "key": "products/7790.../v1.jpg", "url": "https://images.huggian.com/products/..." } ] } /v1/products/{ean}/images Basic+ Subir imagen (multipart/form-data con campo `file`).
Response
201 → { "key": "...", "url": "..." }. - Acepta JPEG, PNG, WebP. Tamaño máximo configurable por entorno.
/i/{key} Free+ Proxy de imagen pública. Sirve los bytes desde el bucket. Sin auth — la URL es la credencial.
Response
Image bytes (Content-Type real). Cacheable por CDN. - No requiere X-Api-Key.
- El servicio rechaza keys que escapen del prefijo `products/`.
Health check
/health Free+ Healthcheck no autenticado.
Response
{ "ok": true } Schemas
Product{
id: uuid,
barcode: string | null,
barcode_type: string, // "EAN13" | "EAN8" | "UPC" | ...
name: string,
description: string | null,
category_id: uuid | null,
brand: string | null,
brand_id: uuid | null,
brand_logo_url: string | null,
image_url: string | null,
ncm_code: string | null,
afip_category: string | null,
country_of_origin: string, // ISO 3166-1 alpha-2
verified: boolean,
source: string, // "manual" | "openff" | ...
presentation: string | null, // "500g", "1L", ...
subcategory: string | null,
unit_of_measure: string | null, // "g" | "ml" | "u"
family_id: uuid | null,
product_type: string, // "good" | "service"
attributes: object | null, // free-form JSON
created_at: datetime,
updated_at: datetime
}Brand{
id: uuid,
name: string,
slug: string, // normalizado: sin acentos, lowercase
logo_url: string | null,
created_at: datetime
}ProductFamily{
id: uuid,
name: string,
slug: string,
brand_id: uuid | null,
created_at: datetime
}Category{
id: uuid,
slug: string,
name: string,
parent_id: uuid | null, // null para categorías raíz
created_at: datetime
}