CMS y webs
Imágenes de producto en Magento 2 por la REST API
Sube el maestro con POST /rest/V1/products/<sku>/media, genera cada medida con SocialCutter y asígnalas como mediaGalleryEntries.
- Magento 2
- REST API
- Bearer token
- media_gallery_entries
- base64
- imágenes de producto
- Python
Por qué pre-generar las medidas antes de subir
Una ficha de producto de Magento se ve en la rejilla de categoría, en la ficha, en el buscador, en el carrito y en el widget de producto relacionado. El tema aplica sus propias proporciones y recorta el maestro al vuelo. Eso vale mientras no necesites una medida concreta.
El flujo correcto es: entra un maestro, SocialCutter devuelve cada medida y Magento recibe la que toca en cada sitio. El recorte de cover, el modo por defecto, es centrado: escala y recorta el sobrante a partes iguales por los dos lados. No hay detección de sujeto ni nada parecido, así que deja aire en los bordes del maestro.
| Dónde va en la tienda | Destino SocialCutter | Medida |
|---|---|---|
| Imagen principal | instagram post | 1080x1080 (1:1) |
| Imagen vertical de ficha | instagram story | 1080x1920 (9:16) |
| Banner de categoría | facebook post | 1200x630 (1.91:1) |
| Cabecera del CMS | twitter header | 1500x500 (3:1) |
| Miniatura de vídeo de producto | youtube thumbnail | 1280x720 (16:9) |
Los formatos y medidas reales salen de GET /api/v1/platforms, que es público. El catálogo no tiene un formato 4:5: lo cuadrado es 1:1 y lo vertical es 9:16.
Antes de empezar: integración, token y permisos
Base y versión. Las llamadas van a https://tu-tienda.com/rest/V1/..., o a https://tu-tienda.com/rest/<store_code>/V1/... si tienes varias tiendas. Los nombres de los campos y la disponibilidad de los endpoints cambian entre 2.3 y 2.4, así que fija la versión que tienes instalada y contrástala con la referencia oficial: https://developer.adobe.com/commerce/webapi/rest/
Autenticación. Hay dos tokens y los dos viajan como Authorization: Bearer <token>:
- Integración. En el admin, System → Extensions → Integrations. Al activarla se genera el Access Token, que no caduca salvo que lo revoques. Es el que conviene para jobs automáticos.
- Admin.
POST /rest/V1/integration/admin/tokencon{"username","password"}devuelve el token como una cadena JSON. Caduca según la vida útil configurada en la tienda.
Permisos. El rol de la integración decide a qué recursos puede escribir. Para este flujo necesita acceso a productos y a la Media Gallery del catálogo (lectura y escritura). Si el token no cubre esos recursos, verás 401 Unauthorized o 403 Forbidden aunque el token sea válido: revisa el rol, no la clave.
export MAGENTO_URL="https://tu-tienda.com"
export MAGENTO_TOKEN="eyJraWQ..." # Access Token de la integración
export SC_KEY="sc_tu_clave"
export SKU="CAM-2026-01"
1. Genera las medidas con SocialCutter
curl -s -X POST "https://api.socialcutter.theboomer.dev/api/v1/images/process" \
-H "X-API-Key: *** \
-H "Content-Type: application/json" \
-H "Idempotency-Key: magento-$SKU" \
-d '{
"source": { "type": "url", "value": "https://tu-cdn.com/maestro.jpg" },
"destinations": [
{ "platform": "instagram", "format": "post" },
{ "platform": "instagram", "format": "story" },
{ "platform": "twitter", "format": "header" }
],
"options": { "quality": 90, "format": "jpg" }
}' > sc.json
jq -r '.id, (.outputs[] | "\(.platform)/\(.format) \(.width)x\(.height) \(.url)")' sc.json
Cada salida trae url, width, height y size_bytes. Para un maestro local usa POST /api/v1/images/process/upload (multipart, campo file, máximo 5 MB). Para lotes de SKU, POST /api/v1/images/batch. La cabecera Idempotency-Key hace seguros los reintentos.
2. Sube cada fichero al producto (base64)
El endpoint de media acepta JSON, así que el fichero va codificado. El sku con barras se codifica en la URL (10000/100/S → 10000%2F100%2FS).
SQUARE=$(jq -r '.outputs[0].url' sc.json)
curl -s "$SQUARE" -o square.jpg
B64=$(base64 -w0 square.jpg)
curl -s -X POST "$MAGENTO_URL/rest/V1/products/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote_plus(sys.argv[1]))" "$SKU")/media" \
-H "Authorization: Bearer $MAGENTO_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"entry\":{
\"media_type\":\"image\",
\"label\":\"Camiseta vista frontal\",
\"position\":1,
\"disabled\":false,
\"types\":[\"image\",\"small_image\",\"thumbnail\"],
\"file\":\"camiseta-frontal.jpg\",
\"content\":{
\"base64_encoded_data\":\"$B64\",
\"type\":\"image/jpeg\",
\"name\":\"camiseta-frontal.jpg\"
}}}"
La respuesta trae el file (ruta relativa dentro de pub/media/catalog/product) y el id de la entrada. Marca image, small_image y thumbnail en una sola imagen: es la que Magento usa como principal. El base64 engorda el fichero un 33 %; si tu PHP tiene un post_max_size bajo, sube solo las salidas necesarias o comprime antes.
3. Reconstruye la galería con media_gallery_entries
Para ordenar y fijar la principal se hace un PUT al producto con media_gallery_entries. Es un reemplazo total: cada entrada que no incluyas desaparece. Lee primero las que ya existen (GET /rest/V1/products/<sku>/media) y móntalas todas.
jq -n --arg f "camiseta-frontal.jpg" '{product:{media_gallery_entries:[
{ id: 42, media_type:"image", label:"Camiseta vista frontal",
position:1, disabled:false, types:["image","small_image","thumbnail"], file:$f }
]}}' > payload.json
curl -s -X PUT "$MAGENTO_URL/rest/V1/products/$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote_plus(sys.argv[1]))" "$SKU")" \
-H "Authorization: Bearer $MAGENTO_TOKEN" \
-H "Content-Type: application/json" \
-d @payload.json | jq '.media_gallery_entries[] | {id, position, types, file}'
Documentación de la referencia REST (endpoints y estructuras, revisa la versión que tengas): https://developer.adobe.com/commerce/webapi/rest/
4. El mismo flujo en Python
import base64, json, urllib.parse, requests
SC = "https://api.socialcutter.theboomer.dev/api/v1/images/process"
API = "https://tu-tienda.com/rest/V1"
SKU = "CAM-2026-01"
HDR = {"Authorization": "Bearer eyJraWQ...", "Content-Type": "application/json"}
salidas = requests.post(
SC,
headers={"X-API-Key": "sc_...", "Content-Type": "application/json"},
json={"source": {"type": "url", "value": "https://tu-cdn.com/maestro.jpg"},
"destinations": [{"platform": "instagram", "format": "post"},
{"platform": "instagram", "format": "story"}]},
timeout=60,
).json()["outputs"]
sku_url = urllib.parse.quote_plus(SKU)
for pos, salida in enumerate(salidas, start=1):
contenido = base64.b64encode(requests.get(salida["url"], timeout=60).content).decode()
requests.post(
f"{API}/products/{sku_url}/media",
headers=HDR,
data=json.dumps({"entry": {
"media_type": "image",
"label": f"Producto {SKU} {salida['format']}",
"position": pos,
"disabled": False,
"types": ["image", "small_image", "thumbnail"] if pos == 1 else [],
"file": f"{SKU}-{salida['format']}.jpg",
"content": {"base64_encoded_data": contenido,
"type": "image/jpeg",
"name": f"{SKU}-{salida['format']}.jpg"}}}),
timeout=120,
).raise_for_status()
print("Subidas", len(salidas), "imágenes a", SKU)
Coste
- 1 uso por destino (plataforma y formato) por petición; los repetidos no se cobran dos veces.
- Los procesamientos fallidos se devuelven.
- Todos los planes incluyen API y MCP: Free 3 usos/día, Basic 10, Pro 30, Agency 100, desde 0 / 3 / 9 / 29 EUR al mes.
Errores típicos
| Síntoma | Causa | Solución |
|---|---|---|
401 Unauthorized | Token caducado o mal copiado | Renueva el token o el de integración |
403 Forbidden | El rol no cubre Catalog ni Media Gallery | Edita los recursos de la integración |
400 con “Decoding failed” | Base64 partido en líneas | Codifica sin saltos: base64 -w0 |
404 en el producto | SKU con caracteres sin codificar | Codifica el SKU (quote_plus) |
| La imagen no se ve | Tipos vacíos o caché sin limpiar | Pon image/small_image/thumbnail y vacía la caché |
| Desaparecen fotos antiguas | media_gallery_entries parcial | Reconstruye la lista completa |
413 en SocialCutter | El maestro supera 5 MB | Reduce la imagen antes de subirla |
429 en SocialCutter | Cuota del monedero agotada | Consulta tu cuota en el dashboard o sube de plan |
Sin código
Un automatizador (n8n, Make, Zapier) encadena los mismos pasos: disparador de catálogo, nodo HTTP a /api/v1/images/process y un nodo HTTP a la REST de Magento con el token en Bearer. El patrón está en la guía de automatización con n8n.
Siguientes pasos
- Terminal: Procesa imágenes con la API desde la terminal (curl)
- Python: Procesa imágenes con la API desde Python
- Shopify: Integra SocialCutter con la Admin API de Shopify
- WooCommerce: Sube imágenes de catálogo a WooCommerce
- Automatización: Automatiza el recorte de imágenes para redes sociales
- Documentación: https://docs.socialcutter.theboomer.dev
Preguntas frecuentes
¿Con qué token me autentico, integración o admin?
Con el Access Token de una integración creada en System → Extensions → Integrations: se genera al activarla y se envía como Authorization: Bearer. El token de admin, que se pide a POST /rest/V1/integration/admin/token, también vale, pero caduca según la configuración de la tienda.
¿Por qué tengo que codificar la imagen en base64?
Porque el cuerpo de POST /rest/V1/products/<sku>/media es JSON y no admite binario. El campo entry.content.base64_encoded_data lleva el fichero codificado, y con él va el mime type y el nombre. El base64 engorda el fichero un tercio, así que comprueba el límite de subida de tu PHP antes de mandar el maestro.
¿El PUT con media_gallery_entries borra las imágenes que ya tenía el producto?
Sí. Es un reemplazo de la galería completa: si mandas una lista parcial pierdes las entradas que no incluyas. Reconstruye el array con las fotos antiguas más las nuevas antes de guardar.
¿Magento no genera ya sus propios tamaños?
Sí, el tema define las medidas de ficha, categoría y miniatura. Pre-genera con SocialCutter cuando necesites una medida exacta que el tema no produce, o cuando el mismo maestro alimente canales fuera de la web.
¿Qué cuesta preparar las imágenes de un producto?
1 uso por destino, es decir por cada par de plataforma y formato. El maestro entra una vez y cada medida pedida suma un uso; los destinos repetidos en la misma petición no se cobran dos veces.