API y desarrollo
Procesa imágenes con la API de SocialCutter desde Python
Cliente de la API de SocialCutter en Python con requests: salud, plataformas, procesado por URL y fichero, lotes, historial, monedero y reintentos con backoff.
- Python
- requests
- API
- SocialCutter
- procesar imagenes
- multipart
- API key
Requisitos
Instala requests y guarda las credenciales en variables de entorno:
pip install requests
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 de la API está en https://docs.socialcutter.theboomer.dev.
Cliente mínimo
Una sesión reutilizable evita repetir la cabecera y las credenciales en cada llamada:
import os
import requests
API_URL = os.environ.get("SOCIALCUTTER_API_URL", "https://api.socialcutter.theboomer.dev")
API_KEY = os.environ["SOCIALCUTTER_API_KEY"] # nunca la escribas en el codigo
session = requests.Session()
session.headers.update({"X-API-Key": API_KEY})
def get(path, **params):
r = session.get(f"{API_URL}{path}", params=params, timeout=30)
r.raise_for_status()
return r.json()
def post_json(path, payload):
r = session.post(f"{API_URL}{path}", json=payload, timeout=60)
r.raise_for_status()
return r.json()
raise_for_status() lanza una excepción con el código HTTP cuando la respuesta no es 2xx, así no se procesan respuestas de error como si fueran válidas.
Salud y credenciales
# Publico: no requiere clave
print(get("/api/v1/health")) # status, version, uptime, database
# Identidad de la cuenta autenticada
print(get("/api/v1/auth/me"))
# Usos disponibles
print(get("/api/v1/credits"))
/api/v1/health devuelve el estado del servicio y la conexión con la base de datos. Si /api/v1/auth/me responde 401, la clave falta, está mal formada o fue revocada.
Listar plataformas y formatos
data = get("/api/v1/platforms")
for plataforma, formatos in data["platforms"].items():
for f in formatos:
print(plataforma, f["format"], f"{f['width']}x{f['height']}", f["aspect_ratio"])
/platforms, /formats y /fit-modes son públicos. Úsalos para construir la lista de destinos sin codificar medidas a mano.
Procesar una imagen por URL
resp = post_json("/api/v1/images/process", {
"source": {"type": "url", "value": "https://example.com/foto.jpg"},
"destinations": [
{"platform": "instagram", "format": "post"},
{"platform": "tiktok", "format": "cover"},
],
})
print(resp["id"], resp["status"])
Procesar un fichero local (multipart)
with open("foto.jpg", "rb") as fh:
r = session.post(
f"{API_URL}/api/v1/images/process/upload",
files={"file": ("foto.jpg", fh, "image/jpeg")},
data={"destinations": '[{"platform":"linkedin","format":"post"}]'},
timeout=60,
)
r.raise_for_status()
resp = r.json()
En multipart el fichero va en el campo file y destinations es una cadena JSON en un campo del formulario. El límite de subida es 5 MB; por encima la API responde 413.
Procesar un lote
lote = post_json("/api/v1/images/batch", {
"images": [
{"source": {"type": "url", "value": "https://example.com/a.jpg"},
"destinations": [{"platform": "instagram", "format": "post"}]},
{"source": {"type": "url", "value": "https://example.com/b.jpg"},
"destinations": [{"platform": "twitter", "format": "post"}]},
]
})
Leer la respuesta y descargar
La respuesta trae el identificador del trabajo en id, un status y una lista outputs, una entrada por destino con platform, format, url, width, height y size_bytes. Los tiempos van en metadata.
resp = post_json("/api/v1/images/process", {
"source": {"type": "url", "value": "https://example.com/foto.jpg"},
"destinations": [{"platform": "instagram", "format": "post"}],
})
for out in resp["outputs"]:
print(out["platform"], out["format"], f"{out['width']}x{out['height']}")
img = session.get(out["url"], stream=True, timeout=60)
img.raise_for_status()
with open(f"{out['platform']}-{out['format']}.webp", "wb") as fh:
for chunk in img.iter_content(8192):
fh.write(chunk)
Historial y monedero
# Ultimas 10 imagenes
print(get("/api/v1/history", limit=10))
# Solo las creadas desde la API
print(get("/api/v1/history", origin="api", limit=10))
# Cuota diaria, bolsa extra y saldo comprado
print(get("/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
Captura el código de estado y decide si merece un reintento. Los errores de cliente (4xx, salvo 429) no se reintentan: repetir la misma petición no la va a arreglar.
import time
def con_reintentos(fn, intentos=4, base=0.5):
for i in range(intentos):
try:
return fn()
except requests.HTTPError as e:
code = e.response.status_code
if code == 429 or code >= 500:
if i == intentos - 1:
raise
time.sleep(base * (2 ** i)) # 0.5, 1, 2, 4 s
continue
raise
con_reintentos(lambda: post_json("/api/v1/images/process", payload))
Para reintentos automáticos en toda la sesión, requests delega en urllib3.Retry, que aplica espera exponencial y respeta la cabecera Retry-After:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=4,
backoff_factor=0.5,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=frozenset(["GET", "POST"]),
respect_retry_after_header=True,
)
session.mount("https://", HTTPAdapter(max_retries=retry))
Patrón documentado en https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry. Los nombres y opciones pueden cambiar entre versiones de la librería; confírmalos en esa documentación.
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 Node: Procesa imágenes con la API de SocialCutter desde Node.js
- 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
¿Qué librería necesito en Python?
requests, que instala con pip install requests. Para el multipart no hace falta nada más: requests arma el formulario si pasas files y data.
¿Dónde pongo la API key?
En la variable de entorno SOCIALCUTTER_API_KEY, nunca en el código. Se envía en cada petición en la cabecera X-API-Key.
¿Cómo subo una imagen que está en disco?
Con POST /api/v1/images/process/upload usando files={'file': ...} y destinations como campo de formulario en JSON. El límite es 5 MB.
¿Cómo se cobra un lote?
1 uso por destino (combinación de plataforma y formato) y por imagen. Un lote de 3 imágenes con 2 destinos cada una son 6 usos.
¿Por qué recibo 429?
Porque la cuota del monedero está agotada. Consulta GET /api/v1/credits y GET /api/v1/wallet antes de lotes grandes.