API y desarrollo
Procesa imágenes con la API de SocialCutter desde Node.js
Cliente de la API de SocialCutter en Node 18 o superior con fetch y FormData: salud, plataformas, procesado por URL y fichero, lote de una carpeta y errores.
- Node.js
- fetch
- FormData
- API
- SocialCutter
- procesar imagenes
- API key
Requisitos
Node 18 o superior. En esa versión fetch, FormData, Blob y Headers son globales, así que no hace falta ningún paquete externo. Guarda las credenciales en variables de entorno:
export SOCIALCUTTER_API_URL="https://api.socialcutter.theboomer.dev"
export SOCIALCUTTER_API_KEY="sc_tu_clave"
La clave se crea en https://dash.socialcutter.theboomer.dev, en Perfil → API keys. Empieza por sc_ y se muestra una sola vez. La referencia completa está en https://docs.socialcutter.theboomer.dev.
Cliente mínimo
const API_URL = process.env.SOCIALCUTTER_API_URL ?? 'https://api.socialcutter.theboomer.dev'
const API_KEY = process.env.SOCIALCUTTER_API_KEY // nunca la escribas en el codigo
async function call(path, init = {}) {
const res = await fetch(`${API_URL}${path}`, {
...init,
headers: { 'X-API-Key': API_KEY, ...(init.headers ?? {}) }
})
if (!res.ok) {
const body = await res.text()
throw new Error(`HTTP ${res.status}: ${body}`)
}
return res.json()
}
const getJson = (path) => call(path)
const postJson = (path, payload) =>
call(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })
fetch no lanza excepción en respuestas 4xx o 5xx: hay que comprobar res.ok explícitamente, como arriba.
Salud y credenciales
// Publico: no requiere clave
console.log(await getJson('/api/v1/health')) // status, version, uptime, database
// Identidad de la cuenta autenticada
console.log(await getJson('/api/v1/auth/me'))
// Usos disponibles
console.log(await getJson('/api/v1/credits'))
Si /api/v1/auth/me responde 401, la clave falta, está mal formada o fue revocada.
Listar plataformas y formatos
const { platforms } = await getJson('/api/v1/platforms')
for (const [nombre, formatos] of Object.entries(platforms)) {
for (const f of formatos) {
console.log(nombre, f.format, `${f.width}x${f.height}`, f.aspect_ratio)
}
}
/platforms, /formats y /fit-modes son públicos. Úsalos para construir los destinos sin codificar medidas a mano.
Procesar una imagen por URL
const resp = await postJson('/api/v1/images/process', {
source: { type: 'url', value: 'https://example.com/foto.jpg' },
destinations: [
{ platform: 'instagram', format: 'post' },
{ platform: 'tiktok', format: 'cover' }
]
})
console.log(resp.id, resp.status)
Procesar un fichero local (multipart)
import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'
async function procesarFichero(ruta) {
const buffer = await readFile(ruta)
const form = new FormData()
form.append('file', new Blob([buffer], { type: 'image/jpeg' }), basename(ruta))
form.append('destinations', JSON.stringify([{ platform: 'linkedin', format: 'post' }]))
// No fijes Content-Type a mano: fetch pone el boundary correcto
return call('/api/v1/images/process/upload', { method: 'POST', body: form })
}
Deja que fetch calcule el Content-Type: si lo fijas tú, el boundary del multipart se pierde y la API no podrá leer el fichero. El límite de subida es 5 MB; por encima la API responde 413.
Script de lote leyendo una carpeta
import { readdir } from 'node:fs/promises'
import { join, extname } from 'node:path'
const EXT = new Set(['.jpg', '.jpeg', '.png', '.webp'])
async function loteDesdeCarpeta(carpeta, destinations) {
const ficheros = (await readdir(carpeta)).filter((f) => EXT.has(extname(f).toLowerCase()))
const images = []
for (const f of ficheros) {
const buffer = await readFile(join(carpeta, f))
const b64 = buffer.toString('base64')
images.push({
source: { type: 'base64', value: b64 },
destinations
})
}
return postJson('/api/v1/images/batch', { images })
}
const salida = await loteDesdeCarpeta('./fotos', [{ platform: 'instagram', format: 'post' }])
console.log(`Lote con ${salida.length ?? 'varios'} resultados`)
Recuerda: 1 uso por destino y por imagen. Un lote de 10 imágenes con 2 destinos cada una son 20 usos.
Leer la respuesta y descargar
La respuesta trae el identificador del trabajo en id, un status y outputs, una entrada por destino con platform, format, url, width, height y size_bytes.
import { writeFile } from 'node:fs/promises'
for (const out of resp.outputs) {
console.log(out.platform, out.format, `${out.width}x${out.height}`)
const img = await fetch(out.url)
const bytes = Buffer.from(await img.arrayBuffer())
await writeFile(`${out.platform}-${out.format}.webp`, bytes)
}
Historial y monedero
// Ultimas 10 imagenes
console.log(await getJson('/api/v1/history?limit=10'))
// Solo las creadas desde la API
console.log(await getJson('/api/v1/history?origin=api&limit=10'))
// Cuota diaria, bolsa extra y saldo comprado
console.log(await getJson('/api/v1/wallet'))
limit admite de 1 a 100 y skip sirve para paginar. El filtro origin distingue browser (dashboard) de api.
Errores y reintentos con backoff
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
async function conReintentos(fn, intentos = 4, base = 500) {
for (let i = 0; i < intentos; i++) {
try {
return await fn()
} catch (e) {
const code = Number((e.message.match(/HTTP (\d+)/) ?? [])[1])
if (code === 429 || code >= 500) {
if (i === intentos - 1) throw e
await sleep(base * 2 ** i) // 500, 1000, 2000, 4000 ms
continue
}
throw e
}
}
}
No reintentes errores 400, 401, 404 o 422: repetir la misma petición no la arregla. Solo 429 (cuota) y 5xx (fallo temporal) merecen otro intento.
Coste
- 1 uso por destino (plataforma y formato) por petición.
- Destinos repetidos en la misma petición no se cobran dos veces.
- Los procesamientos fallidos se devuelven.
Errores típicos
| Código | Significado |
|---|---|
| 400 | Payload inválido: plataforma, formato o modo desconocido, JSON mal formado, o clave ya activa |
| 401 | Credenciales ausentes, mal formadas, caducadas o revocadas |
| 404 | Recurso no encontrado (id de imagen o de fichero) |
| 413 | El fichero supera 5 MB |
| 422 | Error de validación de la petición |
| 429 | Cuota del monedero agotada |
| 500 | Fallo de procesamiento; los usos de esa petición se devuelven |
Siguientes pasos
- Guía de terminal: Procesa imágenes con la API desde la terminal (curl)
- Guía de Python: Procesa imágenes con la API de SocialCutter desde Python
- Guía de PHP: Procesa imágenes con la API de SocialCutter desde PHP
- Documentación de la API: https://docs.socialcutter.theboomer.dev
- Dashboard: https://dash.socialcutter.theboomer.dev
Preguntas frecuentes
¿Necesito instalar algún paquete?
No. Node 18 o superior ya trae fetch, FormData y Blob de forma global. Los ejemplos funcionan sin dependencias externas.
¿Dónde pongo la API key?
En la variable de entorno SOCIALCUTTER_API_KEY y la lees con process.env. Se envía en cada petición en la cabecera X-API-Key.
¿Cómo subo un fichero local sin instalar nada?
Con fetch y FormData: añades el Blob del fichero en el campo file y destinations como cadena JSON. El límite es 5 MB.
¿Cómo proceso una carpeta entera?
Recorre la carpeta con fs.readdir, filtra las extensiones de imagen y llama a /api/v1/images/batch con un array de images.
¿Por qué recibo 429?
Porque la cuota del monedero está agotada. Comprueba GET /api/v1/credits y GET /api/v1/wallet antes de lanzar el lote.