# Contentful: cuándo pre-generar imágenes y cómo publicarlas
> La Images API de Contentful ya recorta al vuelo: mira cuándo conviene pre-generar los ficheros con SocialCutter y cómo publicar el asset por la Management API.
- URL: https://socialcutter.theboomer.dev/guias/contentful/
- Idioma: es
- Familia: cms
- Actualizado: 2026-09-24
- Palabras clave: Contentful, Images API, Content Management API, assets, uploadFrom, imágenes para redes sociales, pre-generar medidas
## Lo que Contentful ya hace por su cuenta

Contentful tiene su propia Images API: una API **de solo lectura** servida desde `images.ctfassets.net` a la que se le añaden parámetros de consulta a la URL del fichero (`fields.file.url`) para transformar la imagen al vuelo. No hay que subir nada nuevo ni generar ficheros: cambias la URL y el CDN devuelve la versión transformada.

| Parámetro | Qué hace | Valores |
|---|---|---|
| `w` / `h` | Ancho y alto en píxeles | máximo 4000 px |
| `fit` | Comportamiento del ajuste | `pad`, `fill`, `scale`, `crop`, `thumb` |
| `f` | Foco del encuadre cuando usas `pad`, `fill`, `crop` o `thumb` | el valor por defecto es `center` |
| `fm` | Formato de salida | `jpg`, `png`, `webp`, `gif`, `avif`, `tiff`; por defecto, el original |
| `q` | Calidad | entero de 1 a 100 |
| `bg` | Color de fondo del `pad` y del redondeo | valores RGB, por ejemplo `rgb:9090ff` |
| `r` | Esquinas redondeadas o recorte circular | píxeles, o `max` |
| `fl` | Variantes concretas | `progressive` para JPEG, `png8` para PNG de 8 bits |

Un ejemplo de URL de salida:

```text
https://images.ctfassets.net/SPACE_ID/ASSET_ID/TOKEN/nombre.jpg?w=1080&h=1080&fit=fill&fm=webp&q=85
```

Los assets publicados no necesitan autenticación en la Images API, así que cualquiera puede pedir esas transformaciones desde tu web. Con `fit=fill` y el foco por defecto (`center`) obtienes un recorte centrado equivalente al de SocialCutter. Y si la imagen original supera los 100 MB, Contentful la trata como asset y no aplica transformaciones.

**Conclusión honesta: para tu web y tu blog, casi nunca necesitas pre-generar nada.** Una sola llamada bien formada desde la plantilla resuelve el problema. Pre-generar tiene sentido en otro escenario.

## Dónde se queda corta para redes sociales

La Images API devuelve una **URL transformada**. No devuelve un fichero descargable preparado para subir. Eso no es un defecto: es que resuelve otro problema.

Cuando publicas en Instagram, TikTok, YouTube o LinkedIn, la red no se descarga tu URL: tú le subes un fichero. Y los programadores de publicaciones, las herramientas de campañas, los partners y los dosieres de cliente casi siempre piden un fichero con su nombre y su peso. Ahí no hay parámetro que valga.

## Cuándo merece la pena pre-generar con SocialCutter

| Caso | Por qué | Destino habitual |
|---|---|---|
| Piezas para redes que no pasan por el CDN de Contentful | La red necesita un fichero con la medida exacta, no una URL | `instagram` `post`, `instagram` `story`, `tiktok` `cover` |
| Programadores de publicaciones y herramientas de campañas | Solo aceptan una subida de fichero | `facebook` `post`, `linkedin` `post`, `twitter` `post` |
| Campañas fuera de Contentful (pago, email, partners) | El material sale del CMS y se entrega en mano | `youtube` `thumbnail`, `twitter` `header` |
| Exportaciones y entregas a cliente | Hace falta un paquete de ficheros con nombres legibles y un peso controlado | Todos los de la campaña |
| Un maestro que alimenta varios canales | Un solo diseño, una salida por canal, sin tocar la plantilla | `linkedin` `cover`, `facebook` `cover` |
| Partners que no pueden usar URLs con parámetros | Su sistema no construye la transformación | Cualquiera |

El flujo es siempre el mismo: **entra un maestro, SocialCutter devuelve cada medida y el fichero se publica o se entrega**. El recorte de `cover`, el modo por defecto, es **centrado**: escala y recorta el sobrante a partes iguales por los dos lados, sin analizar la imagen. Deja aire en los bordes del maestro.

## Cuándo NO hace falta pre-generar

| Caso | Qué usar en su lugar |
|---|---|
| La imagen solo se sirve desde tu web o tu blog | Los parámetros de la Images API (`w`, `h`, `fit`, `fm`, `q`); no consume usos |
| El tema o la plantilla ya aplica su proporción | Nada: no dupliques assets |
| Solo quieres distintas resoluciones para distintas pantallas | El mismo asset con `w`+`fit` y un `srcset` |
| Archivo histórico de la marca | Guarda el maestro sin recortar y genera al publicar |
| Necesitas retoque, fondo transparente o texto compuesto | Un editor de fotos: no es lo que hace SocialCutter |

Un aviso que evita un problema caro: **no sustituyas el maestro de Contentful por una salida recortada**. Si el asset principal de la campaña pasa a ser un 1080x1080, el banner de YouTube de 2560x1440 saldrá ampliado y borroso. El maestro se queda como maestro; las medidas se generan en el momento de publicar.

## Generar las medidas de una campaña

```bash
curl -s -X POST "https://api.socialcutter.theboomer.dev/api/v1/images/process" \
  -H "X-API-Key: sc_tu_clave" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: contentful-campana-2026-09" \
  -d '{
    "source": { "type": "url", "value": "https://images.ctfassets.net/SPACE_ID/ASSET_ID/TOKEN/maestra.jpg" },
    "destinations": [
      { "platform": "instagram", "format": "post" },
      { "platform": "instagram", "format": "story" },
      { "platform": "linkedin", "format": "post" },
      { "platform": "youtube", "format": "thumbnail" }
    ],
    "options": { "fit_mode": "cover", "format": "jpg", "quality": 85 }
  }' > sc.json

jq -r '.outputs[] | "\(.platform)/\(.format) \(.width)x\(.height) \(.size_bytes) bytes \(.url)"' sc.json
```

Esa petición consume **4 usos** y devuelve cuatro salidas. Fíjate en el detalle útil: puedes usar como `source` la URL del propio CDN de Contentful, así que el maestro sigue viviendo en un único sitio. Cada salida es pública, que es justo lo que necesitas para publicarla en tu red o subirla como asset.

## Publicar el asset con la Content Management API

La [Content Management API](https://www.contentful.com/developers/docs/references/content-management-api/) (CMA) usa la base `https://api.contentful.com` y autenticación `Authorization: Bearer <token>`. Todas las llamadas llevan `Content-Type: application/vnd.contentful.management.v1+json`. Crear un asset son **tres pasos**: crear, procesar y publicar.

```bash
export CF="https://api.contentful.com/spaces/SPACE_ID/environments/ENV_ID"
export CF_TOKEN="CFPAT-..."
export LOCALE="es-ES"
```

### 1. Sube el binario a la Upload API

Si el fichero ya está en una URL pública (por ejemplo, una salida de SocialCutter), puedes saltar este paso y dar la URL directamente en el campo `upload` al crear el asset. Si lo tienes en local, súbelo primero:

```bash
curl -s -X POST "https://upload.contentful.com/spaces/SPACE_ID/environments/ENV_ID/uploads" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @instagram-post.jpg > upload.json

UPLOAD_ID=$(jq -r '.sys.id' upload.json)
```

La respuesta trae el `sys.id` del upload. Ojo con su caducidad: si no lo asocias a un asset y lo procesas en **24 horas**, el fichero y sus metadatos se borran. Los clientes con residencia de datos en la UE usan `upload.eu.contentful.com`.

### 2. Crea el asset apuntando al upload

```bash
curl -s -X POST "$CF/assets" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/vnd.contentful.management.v1+json" \
  -d '{
    "fields": {
      "title": { "es-ES": "Campaña septiembre · Instagram post 1080x1080" },
      "file": {
        "es-ES": {
          "contentType": "image/jpeg",
          "fileName": "campana-septiembre-instagram-post-1080x1080.jpg",
          "uploadFrom": {
            "sys": { "type": "Link", "linkType": "Upload", "id": "'"$UPLOAD_ID"'" }
          }
        }
      }
    }
  }' > asset.json

ASSET_ID=$(jq -r '.sys.id' asset.json)
VERSION=$(jq -r '.sys.version' asset.json)
```

Para elegir tú el ID, usa un `PUT` a `/spaces/SPACE_ID/environments/ENV_ID/assets/ASSET_ID`: crea el asset con ese ID o actualiza el que ya existe. En la actualización, Contentful no fusiona cambios: se envía el recurso completo y hay que mandar la versión actual en la cabecera `X-Contentful-Version` (bloqueo optimista).

El nombre del fichero tiene reglas: solo letras, cifras, puntos, guiones y guiones bajos; cualquier otro carácter se sustituye por un guion bajo. Mejor no confiar en acentos ni símbolos.

### 3. Procesa y publica

```bash
# Procesar: obligatorio antes de publicar
curl -s -o /dev/null -w "%{http_code}\n" -X PUT \
  "$CF/assets/$ASSET_ID/files/$LOCALE/process" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/vnd.contentful.management.v1+json" \
  -H "X-Contentful-Version: $VERSION"

# Publicar
curl -s -X PUT "$CF/assets/$ASSET_ID/published" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/vnd.contentful.management.v1+json" \
  -H "X-Contentful-Version: $VERSION" | jq '{id: .sys.id, publishedVersion: .sys.publishedVersion}'
```

Procesar es el paso que trae el fichero al sistema de Contentful y rellena `fields.file.url`; la llamada puede volver antes de que termine el procesamiento. **Sin procesar no se puede publicar ni previsualizar** el asset en la pestaña Media. Al publicar, el asset queda disponible en la Content Delivery API y, si es una imagen, en `images.ctfassets.net` con los parámetros de transformación.

Con el token de la CMA el límite por defecto es de **7 peticiones por segundo**; si te pasas, la API responde `429` y te dice cuánto esperar en `X-Contentful-RateLimit-Reset`.

### El mismo flujo en Python

```python
import requests

CF = "https://api.contentful.com/spaces/SPACE_ID/environments/ENV_ID"
HDR = {"Authorization": "Bearer CFPAT-...",
       "Content-Type": "application/vnd.contentful.management.v1+json"}
LOCALE = "es-ES"

salidas = requests.post(
    "https://api.socialcutter.theboomer.dev/api/v1/images/process",
    headers={"X-API-Key": "sc_...", "Content-Type": "application/json"},
    json={"source": {"type": "url", "value": "https://images.ctfassets.net/.../maestra.jpg"},
          "destinations": [{"platform": "instagram", "format": "post"},
                           {"platform": "youtube", "format": "thumbnail"}]},
    timeout=60,
).json()["outputs"]

for out in salidas:
    binario = requests.get(out["url"], timeout=60).content
    upload = requests.post(
        "https://upload.contentful.com/spaces/SPACE_ID/environments/ENV_ID/uploads",
        headers={"Authorization": HDR["Authorization"],
                 "Content-Type": "application/octet-stream"},
        data=binario, timeout=120,
    ).json()["sys"]["id"]

    nombre = f"campana-{out['platform']}-{out['format']}-{out['width']}x{out['height']}.jpg"
    asset = requests.post(f"{CF}/assets", headers=HDR, json={"fields": {
        "title": {LOCALE: f"Campaña {out['platform']} {out['format']}"},
        "file": {LOCALE: {"contentType": "image/jpeg", "fileName": nombre,
                          "uploadFrom": {"sys": {"type": "Link", "linkType": "Upload", "id": upload}}}}},
        }, timeout=60).json()

    ver = asset["sys"]["version"]
    requests.put(f"{CF}/assets/{asset['sys']['id']}/files/{LOCALE}/process",
                 headers={**HDR, "X-Contentful-Version": str(ver)}, timeout=60).raise_for_status()
    requests.put(f"{CF}/assets/{asset['sys']['id']}/published",
                 headers={**HDR, "X-Contentful-Version": str(ver)}, timeout=60).raise_for_status()

print("Publicados", len(salidas), "assets")
```

## Coste

- **1 uso por destino** (plataforma y formato) por petición; los repetidos no se cobran dos veces.
- Las transformaciones de la Images API de Contentful no consumen usos: son de Contentful.
- 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 | Qué hacer |
|---|---|---|
| `401` en el CMA | Token ausente, caducado o sin permiso sobre el entorno | Revisa el personal access token y su acceso al entorno |
| `409` / conflicto de versión | `X-Contentful-Version` desactualizado | Vuelve a leer el asset y repite con su versión actual |
| "Cannot publish until processing" | Se intentó publicar sin procesar | Procesa primero el fichero del locale |
| El asset queda en borrador y sin URL | El upload caducó antes de procesarse | Vuelve a subir el binario: el upload expira en 24 horas |
| `422` con caracteres raros en el nombre | `fileName` con acentos o símbolos | Usa solo letras, cifras, puntos, guiones y guiones bajos |
| `429` en el CMA | Más de 7 peticiones por segundo | Espera lo que indique `X-Contentful-RateLimit-Reset` |
| El `Content-Type` vuelve con error | Falta la cabecera de versión de la API | Envía `application/vnd.contentful.management.v1+json` en cada llamada |
| Imagen borrosa en el banner de YouTube | Se guardó una salida recortada como maestro | Conserva el maestro y genera el banner desde él |
| `413` en SocialCutter | El maestro supera los 5 MB | Reduce el maestro o usa la URL del CDN como `source` |

## Siguientes pasos

- Medidas: [Medidas de redes sociales: tamaños y proporciones](/guias/medidas-redes-sociales/)
- Formatos: [PNG, JPG o WebP: qué formato usar en cada red](/guias/formatos-redes-sociales/)
- Automatización: [Automatizar imágenes para redes sociales: los 4 caminos reales](/guias/automatizar-imagenes-redes-sociales/)
- Webflow: [Sube imágenes a Webflow y usa SocialCutter](/guias/webflow/)
- WordPress: [Integra SocialCutter con WordPress y WooCommerce](/guias/wordpress/)
- Referencia de la Management API: https://www.contentful.com/developers/docs/references/content-management-api/
- Referencia de la Images API: https://www.contentful.com/developers/docs/references/images-api/