CMS y webs
Integra SocialCutter con la Admin API de Shopify
Flujo con la Admin API de Shopify: sube el maestro a Files y al producto, asigna la salida 1:1 y controla version, scopes y errores. Snippet en Node con fetch.
- Shopify
- Admin API
- GraphQL
- write_products
- write_files
- imagenes de producto
- Node
Por qué una sola imagen maestra
Una ficha de producto vive en varios sitios: la rejilla del catálogo, la página del producto, la vista móvil y la colección. Cada hueco pide una proporción distinta; recortar una copia por hueco deja ficheros duplicados.
El flujo es: un maestro entra, SocialCutter devuelve cada medida, Shopify recibe la que toca en cada hueco. El recorte de cover (modo por defecto) es centrado: escala y recorta el exceso por igual a los dos lados.
| Uso en la tienda | Destino SocialCutter | Medida |
|---|---|---|
| Imagen principal del producto | instagram post | 1080x1080 (1:1) |
| Segunda imagen de producto | instagram story | 1080x1920 (9:16) |
| Banner de colección | facebook post | 1200x630 (1.91:1) |
| Cabecera de la tienda | twitter header | 1500x500 (3:1) |
| Miniatura de vídeo | youtube thumbnail | 1280x720 (16:9) |
Los formatos y medidas salen de GET /api/v1/platforms, que es público.
Nota: el catálogo de SocialCutter no incluye un formato 4:5. Lo vertical es 9:16 (1080x1920) y lo cuadrado es 1:1 (1080x1080). Usa 1:1 como imagen principal y 9:16 como segunda; si necesitas 4:5 exacto, recorta fuera de SocialCutter.
Antes de empezar: versión y scopes
Versión de la API. La Admin API se versiona en la URL y cada versión vive un año. Al escribir esta guía la documentación marca 2026-07 como la última. Fija una versión en tus llamadas:
https://tu-tienda.myshopify.com/admin/api/2026-07/graphql.json
Shopify publica una versión nueva cada trimestre y retira las antiguas. Antes de subir, revisa https://shopify.dev/docs/api/versioning y comprueba que los argumentos que usas no han cambiado.
Scopes. Una app pública o personalizada pide los permisos en su configuración y los recibe al instalarse:
| Scope | Para qué lo necesitas aquí |
|---|---|
write_products | productUpdate con media y productVariantsBulkUpdate |
write_files | fileCreate, para crear ficheros en la página Files |
read_products | Solo si únicamente lees productos |
Los scopes se conceden en la instalación, no por petición: consulta currentAppInstallation y su campo accessScopes para ver los reales. Docs: https://shopify.dev/docs/api/usage/access-scopes
Autenticación. El token va en la cabecera X-Shopify-Access-Token. Referencia: https://shopify.dev/docs/api/usage/authentication
export SHOP="tu-tienda.myshopify.com"
export SHOPIFY_TOKEN="shpat_..."
export API_VERSION="2026-07"
export SC_KEY="sc_tu_clave"
1. Procesar el maestro con SocialCutter
curl -s -X POST "https://api.socialcutter.theboomer.dev/api/v1/images/process" \
-H "X-API-Key: $SC_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "url", "value": "https://tu-cdn.com/maestro.jpg" },
"destinations": [
{ "platform": "instagram", "format": "post" },
{ "platform": "instagram", "format": "story" }
]
}' > sc.json
jq '.image_id, (.outputs[] | {url, platform, format})' sc.json
Cada salida es una URL pública. fileCreate acepta URLs, así que no hace falta descargar nada.
2. Cliente GraphQL en Node
Todas las mutaciones de esta guía son de la GraphQL Admin API. Un cliente mínimo con fetch:
const SHOP = 'tu-tienda.myshopify.com'
const VERSION = '2026-07'
const TOKEN = process.env.SHOPIFY_TOKEN
async function gql(query, variables = {}) {
const res = await fetch(`https://${SHOP}/admin/api/${VERSION}/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': TOKEN
},
body: JSON.stringify({ query, variables })
})
const json = await res.json()
if (json.errors) throw new Error(JSON.stringify(json.errors))
return json.data
}
json.errors son errores de GraphQL (consulta mal formada, campo inexistente, throttle); los fallos de negocio llegan aparte en userErrors. Hay que comprobar los dos.
3. Crear los ficheros en Files
fileCreate admite varias entradas por llamada (el máximo es 250) y devuelve un id por fichero. El procesado es asíncrono: mira fileStatus para saber si terminó.
const FILE_CREATE = `
mutation CreateFiles($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files { id fileStatus alt }
userErrors { field message }
}
}`
const { fileCreate } = await gql(FILE_CREATE, {
files: [
{ originalSource: cuadrado, contentType: 'IMAGE', alt: 'Camiseta, vista frontal' },
{ originalSource: vertical, contentType: 'IMAGE', alt: 'Camiseta, detalle' }
]
})
console.log(fileCreate.files, fileCreate.userErrors)
Requiere write_files. Documentación: https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate
Imágenes por URL, sin subir el binario
Cada salida de SocialCutter ya es una URL pública. En ese caso fileCreate la descarga, la procesa y la almacena por ti: no necesitas stagedUploadsCreate ni tocar el binario. Basta con asignar originalSource a la URL de SocialCutter.
Cuándo hace falta stagedUploadsCreate
stagedUploadsCreate es el flujo en dos pasos para cuando el fichero no está en una URL accesible: vive en tu disco, en una red poco fiable, o es grande y quieres subirlo directo. Devuelve stagedTargets, cada uno con url, resourceUrl y parameters:
const STAGED = `
mutation StagedUploads($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets { url resourceUrl parameters { name value } }
userErrors { field message }
}
}`
const { stagedUploadsCreate } = await gql(STAGED, {
input: [{ filename: 'cuadrado.jpg', mimeType: 'image/jpeg', httpMethod: 'PUT', resource: 'IMAGE' }]
})
const target = stagedUploadsCreate.stagedTargets[0]
La subida a url cambia según el tipo de fichero:
| Tipo | Método de subida |
|---|---|
| Imágenes | PUT a url, con los parameters como cabeceras |
| Vídeos y modelos 3D | POST multipart a url |
Después de subir el binario, el fichero todavía no existe para Shopify: hay que registrarlo con fileCreate usando resourceUrl como originalSource, que es el paso de arriba. Para vídeos y modelos 3D el fileSize es obligatorio en la entrada de stagedUploadsCreate; para imágenes no.
4. Adjuntar la media al producto
productUpdate acepta un argumento media con la lista de ficheros que se añaden al producto. El orden importa: la primera del listado es la imagen principal. Para reordenar después existe productReorderMedia.
const PRODUCT_UPDATE = `
mutation AttachMedia($product: ProductUpdateInput!, $media: [CreateMediaInput!]) {
productUpdate(product: $product, media: $media) {
product { id media(first: 10) { nodes { id alt } } }
userErrors { field message }
}
}`
const PRODUCT_ID = 'gid://shopify/Product/108828309'
await gql(PRODUCT_UPDATE, {
product: { id: PRODUCT_ID },
media: [
{ originalSource: cuadrado, contentType: 'IMAGE', alt: 'Camiseta, vista frontal' },
{ originalSource: vertical, contentType: 'IMAGE', alt: 'Camiseta, detalle' }
]
})
Requiere write_products. Documentación: https://shopify.dev/docs/api/admin-graphql/latest/mutations/productUpdate
Aviso:
productCreateMediayproductUpdateMediasiguen existiendo, pero las versiones recientes de la GraphQL Admin API las marcan como obsoletas. La documentación de media de producto apunta aproductUpdate,productSetoproductCreatecon el argumentomedia. Si tu integración usa las antiguas, planifica la migración.
5. Asociar la media a las variantes
Cada variante se asocia a una media concreta con mediaId (o mediaSrc) para que el selector enseñe la imagen correcta. La mutación es productVariantsBulkUpdate y requiere write_products.
const VARIANT_MEDIA = `
mutation AttachVariantMedia($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
productVariantsBulkUpdate(productId: $productId, variants: $variants) {
productVariants { id }
userErrors { field message }
}
}`
await gql(VARIANT_MEDIA, {
productId: PRODUCT_ID,
variants: [
{ id: 'gid://shopify/ProductVariant/43729076', mediaId: 'gid://shopify/MediaImage/1234' }
]
})
Documentación: https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkUpdate y https://shopify.dev/docs/api/admin-graphql/latest/input-objects/ProductVariantsBulkInput
Coste
- 1 uso por destino (plataforma y formato) por petición; los repetidos no se cobran dos veces.
- Los procesamientos fallidos se devuelven.
- Planes con API y MCP: Free 3 usos/día, Basic 10, Pro 30, Agency 100.
Errores típicos
| Síntoma | Causa | Solución |
|---|---|---|
userErrors con “Access denied” | Falta el scope en la instalación | Añade write_products o write_files y reinstala la app |
THROTTLED en errors | Has agotado el coste del bucket de la API | Aplica backoff y espacia las mutaciones |
| El fichero existe pero no se ve | fileStatus aún no es READY | Es asíncrono: reintenta la lectura pasados unos segundos |
| La imagen principal no es la que quieres | Orden de la lista media | Reordena con productReorderMedia |
originalSource rechazado | La URL no es pública o no es una imagen | Comprueba que apunta a la salida de SocialCutter |
429 en SocialCutter | Cuota del monedero agotada | Consulta GET /api/v1/credits o sube de plan |
Sin código
Un automatizador encadena los mismos pasos con nodos: disparador, nodo HTTP a /api/v1/images/process y nodos de Shopify para subir el fichero y asociarlo al producto. El patrón está en la guía de automatización con n8n.
Siguientes pasos
- WordPress y WooCommerce: Integra SocialCutter con WordPress y WooCommerce
- Ghost: Sube imágenes a Ghost y publica el post correcto
- Node: Procesa imágenes con la API desde Node
- Automatización: Automatiza el recorte de imágenes con n8n
- Documentación: https://docs.socialcutter.theboomer.dev
Preguntas frecuentes
¿Qué versión de la Admin API tengo que usar?
La que fijes en la URL, por ejemplo /admin/api/2026-07/graphql.json. Shopify publica una versión nueva cada trimestre y retira las antiguas, así que fija una versión concreta y súbela a propósito revisando las notas de la release.
¿Qué scopes necesita la app?
write_products para añadir media al producto y actualizar variantes, y write_files para crear ficheros en la pagina Files. Si solo lees, read_products basta. Los scopes se conceden en la instalacion: comprueba los que tiene de verdad con la consulta currentAppInstallation.
¿Cómo me autentico?
Con el Admin API access token de una app personalizada o pública, enviado en la cabecera X-Shopify-Access-Token. Cada peticion va al dominio .myshopify.com de la tienda.
¿fileCreate acepta una URL o tengo que subir el binario?
Acepta una URL publica en originalSource, asi que puedes pasar directamente la salida de SocialCutter. Si el fichero solo existe en tu disco, usa stagedUploadsCreate: devuelve una url con sus parameters y un resourceUrl, sube el binario a esa url (PUT en el caso de imagenes) y pasa el resourceUrl como originalSource de fileCreate.
¿Sigo usando productCreateMedia?
No conviene. Las versiones recientes de la GraphQL Admin API marcan productCreateMedia y productUpdateMedia como obsoletas; la documentacion de media de producto recomienda productUpdate, productSet o productCreate con el argumento media.
¿Cuánto cuesta procesar la imagen de un producto?
1 uso por destino, es decir por cada combinacion de plataforma y formato. Pedir Instagram post y Instagram story para el mismo maestro son 2 usos.