# 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.
- URL: https://socialcutter.theboomer.dev/guias/magento/
- Idioma: es
- Familia: cms
- Actualizado: 2026-09-24
- Palabras clave: 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/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.

```bash
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

```bash
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`).

```bash
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.

```bash
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

```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](/guias/n8n/).

## Siguientes pasos

- Terminal: [Procesa imágenes con la API desde la terminal (curl)](/guias/curl/)
- Python: [Procesa imágenes con la API desde Python](/guias/python/)
- Shopify: [Integra SocialCutter con la Admin API de Shopify](/guias/shopify/)
- WooCommerce: [Sube imágenes de catálogo a WooCommerce](/guias/woocommerce/)
- Automatización: [Automatiza el recorte de imágenes para redes sociales](/guias/automatizar-imagenes-redes-sociales/)
- Documentación: https://docs.socialcutter.theboomer.dev