Saltar al contenido principal
SocialCutter

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 tiendaDestino SocialCutterMedida
Imagen principalinstagram post1080x1080 (1:1)
Imagen vertical de fichainstagram story1080x1920 (9:16)
Banner de categoríafacebook post1200x630 (1.91:1)
Cabecera del CMStwitter header1500x500 (3:1)
Miniatura de vídeo de productoyoutube thumbnail1280x720 (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/token con {"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.

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íntomaCausaSolución
401 UnauthorizedToken caducado o mal copiadoRenueva el token o el de integración
403 ForbiddenEl rol no cubre Catalog ni Media GalleryEdita los recursos de la integración
400 con “Decoding failed”Base64 partido en líneasCodifica sin saltos: base64 -w0
404 en el productoSKU con caracteres sin codificarCodifica el SKU (quote_plus)
La imagen no se veTipos vacíos o caché sin limpiarPon image/small_image/thumbnail y vacía la caché
Desaparecen fotos antiguasmedia_gallery_entries parcialReconstruye la lista completa
413 en SocialCutterEl maestro supera 5 MBReduce la imagen antes de subirla
429 en SocialCutterCuota del monedero agotadaConsulta 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

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.