Documentación
API ez-catalog
Base de datos de productos argentinos. REST sobre HTTPS, JSON, una sola API key.
Base URL: https://api.ez-catalog.huggian.com
Quick start
- Registrate en landing.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 https://api.ez-catalog.huggian.com/v1/products/barcode/7790895000997 \
-H "X-Api-Key: ezc_live_..." Autenticación
Toda request a /v1/* requiere el header
X-Api-Key. Las rutas
/health e
/i/{key} son públicas.
401 Unauthorized— falta o key inválida.403 Forbidden— key válida pero sin permiso (tier muy bajo o sin write).429 Too Many Requests— rate limit excedido. Esperá el próximo minuto.
Planes y límites
| Plan | Rate limit | Lectura | Escritura | Búsqueda full-text |
|---|---|---|---|---|
| Free | 10 rpm | Sí (barcode, brand, family, category) | No | No |
| Basic | 60 rpm | Sí (todo) | Sí | Sí |
| Pro | 300 rpm | Sí (todo) | Sí | Sí |
Las reglas de rate limit son por API key, por minuto, ventana deslizante. Si se downgradea una key, el contador de uso mensual no se resetea — el reseteo es UTC 00:00 del día 1.
Errores
Todos los errores responden JSON con campo error. Algunos agregan message con contexto.
{
"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— tier insuficiente.404— recurso no existe.409— conflicto (barcode/brand duplicado).429— rate limit.500— interno. Reintentar 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://api.ez-catalog.huggian.com/i/brands/taragui.png",
"image_url": "https://api.ez-catalog.huggian.com/i/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://api.ez-catalog.huggian.com/i/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
{
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
} {
id: uuid,
name: string,
slug: string, // normalizado: sin acentos, lowercase
logo_url: string | null,
created_at: datetime
} {
id: uuid,
name: string,
slug: string,
brand_id: uuid | null,
created_at: datetime
} {
id: uuid,
slug: string,
name: string,
parent_id: uuid | null, // null para categorías raíz
created_at: datetime
}