Saltar al contenido principal
SocialCutter

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 tiendaDestino SocialCutterMedida
Imagen principal del productoinstagram post1080x1080 (1:1)
Segunda imagen de productoinstagram story1080x1920 (9:16)
Banner de colecciónfacebook post1200x630 (1.91:1)
Cabecera de la tiendatwitter header1500x500 (3:1)
Miniatura de vídeoyoutube thumbnail1280x720 (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:

ScopePara qué lo necesitas aquí
write_productsproductUpdate con media y productVariantsBulkUpdate
write_filesfileCreate, para crear ficheros en la página Files
read_productsSolo 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:

TipoMétodo de subida
ImágenesPUT a url, con los parameters como cabeceras
Vídeos y modelos 3DPOST 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: productCreateMedia y productUpdateMedia siguen existiendo, pero las versiones recientes de la GraphQL Admin API las marcan como obsoletas. La documentación de media de producto apunta a productUpdate, productSet o productCreate con el argumento media. 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íntomaCausaSolución
userErrors con “Access denied”Falta el scope en la instalaciónAñade write_products o write_files y reinstala la app
THROTTLED en errorsHas agotado el coste del bucket de la APIAplica backoff y espacia las mutaciones
El fichero existe pero no se vefileStatus aún no es READYEs asíncrono: reintenta la lectura pasados unos segundos
La imagen principal no es la que quieresOrden de la lista mediaReordena con productReorderMedia
originalSource rechazadoLa URL no es pública o no es una imagenComprueba que apunta a la salida de SocialCutter
429 en SocialCutterCuota del monedero agotadaConsulta 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

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.