Saltar al contenido principal
SocialCutter

CMS y webs

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.

  • 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ámetroQué haceValores
w / hAncho y alto en píxelesmáximo 4000 px
fitComportamiento del ajustepad, fill, scale, crop, thumb
fFoco del encuadre cuando usas pad, fill, crop o thumbel valor por defecto es center
fmFormato de salidajpg, png, webp, gif, avif, tiff; por defecto, el original
qCalidadentero de 1 a 100
bgColor de fondo del pad y del redondeovalores RGB, por ejemplo rgb:9090ff
rEsquinas redondeadas o recorte circularpíxeles, o max
flVariantes concretasprogressive para JPEG, png8 para PNG de 8 bits

Un ejemplo de URL de salida:

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

CasoPor quéDestino habitual
Piezas para redes que no pasan por el CDN de ContentfulLa red necesita un fichero con la medida exacta, no una URLinstagram post, instagram story, tiktok cover
Programadores de publicaciones y herramientas de campañasSolo aceptan una subida de ficherofacebook post, linkedin post, twitter post
Campañas fuera de Contentful (pago, email, partners)El material sale del CMS y se entrega en manoyoutube thumbnail, twitter header
Exportaciones y entregas a clienteHace falta un paquete de ficheros con nombres legibles y un peso controladoTodos los de la campaña
Un maestro que alimenta varios canalesUn solo diseño, una salida por canal, sin tocar la plantillalinkedin cover, facebook cover
Partners que no pueden usar URLs con parámetrosSu sistema no construye la transformaciónCualquiera

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

CasoQué usar en su lugar
La imagen solo se sirve desde tu web o tu blogLos parámetros de la Images API (w, h, fit, fm, q); no consume usos
El tema o la plantilla ya aplica su proporciónNada: no dupliques assets
Solo quieres distintas resoluciones para distintas pantallasEl mismo asset con w+fit y un srcset
Archivo histórico de la marcaGuarda el maestro sin recortar y genera al publicar
Necesitas retoque, fondo transparente o texto compuestoUn 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

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 (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.

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:

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

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

# 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

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íntomaCausaQué hacer
401 en el CMAToken ausente, caducado o sin permiso sobre el entornoRevisa el personal access token y su acceso al entorno
409 / conflicto de versiónX-Contentful-Version desactualizadoVuelve a leer el asset y repite con su versión actual
“Cannot publish until processing”Se intentó publicar sin procesarProcesa primero el fichero del locale
El asset queda en borrador y sin URLEl upload caducó antes de procesarseVuelve a subir el binario: el upload expira en 24 horas
422 con caracteres raros en el nombrefileName con acentos o símbolosUsa solo letras, cifras, puntos, guiones y guiones bajos
429 en el CMAMás de 7 peticiones por segundoEspera lo que indique X-Contentful-RateLimit-Reset
El Content-Type vuelve con errorFalta la cabecera de versión de la APIEnvía application/vnd.contentful.management.v1+json en cada llamada
Imagen borrosa en el banner de YouTubeSe guardó una salida recortada como maestroConserva el maestro y genera el banner desde él
413 en SocialCutterEl maestro supera los 5 MBReduce el maestro o usa la URL del CDN como source

Siguientes pasos

Preguntas frecuentes

Si Contentful ya redimensiona en la URL, ¿para qué quiero SocialCutter?

Para todo lo que no pasa por su CDN. La Images API devuelve una URL transformada, no un fichero: cuando la red social, el programador de publicaciones, el partner o la entrega al cliente exige un fichero con la medida exacta, ese fichero hay que generarlo aparte.

¿Debo sustituir el maestro de Contentful por la versión recortada?

No. Es un error. Si guardas un 1080x1080 como asset principal, no podrás sacar el banner de YouTube de 2560x1440 sin ampliarlo. Guarda el maestro sin recortar y genera las medidas al publicar.

¿Cuántas llamadas hacen falta para publicar un asset por la API?

Tres: crear el asset, procesarlo y publicarlo. Si el fichero no está en una URL accesible, antes hay una cuarta llamada: subir el binario a la Upload API para obtener un upload_id. Procesar es obligatorio: sin procesar no se puede publicar.

¿Qué va en la cabecera Content-Type de la Management API?

application/vnd.contentful.management.v1+json en las llamadas del CMA, con Authorization: Bearer <token>. Ese Content-Type es el que fija la versión de la API, así que conviene enviarlo siempre en cada llamada en lugar de dejarlo al valor por defecto.

¿Qué cuesta preparar una campaña con SocialCutter?

1 uso por destino, es decir por cada pareja de plataforma y formato. Los destinos repetidos en la misma petición no se cobran dos veces y los procesamientos fallidos se devuelven. Las transformaciones de la Images API de Contentful no consumen usos.

¿Puedo publicar el asset con un ID que yo elija?

Sí. Además del POST que genera el ID automáticamente, la Management API permite crear o actualizar un asset con un ID propio con un PUT a /spaces/{space_id}/environments/{environment_id}/assets/{asset_id}. Es útil para que el ID coincida con el de la campaña.