# 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.
- URL: https://socialcutter.theboomer.dev/guias/python/
- Idioma: es
- Familia: api
- Actualizado: 2026-09-24
- Palabras clave: Python, requests, API, SocialCutter, procesar imagenes, multipart, API key
## Requisitos

Instala `requests` y guarda las credenciales en variables de entorno:

```bash
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:

```python
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

```python
# 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

```python
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

```python
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)

```python
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

```python
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`.

```python
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

```python
# 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.

```python
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`:

```python
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)](/guias/curl/)
- Guía de Node: [Procesa imágenes con la API de SocialCutter desde Node.js](/guias/node/)
- Guía de PHP: [Procesa imágenes con la API de SocialCutter desde PHP](/guias/php/)
- Documentación de la API: https://docs.socialcutter.theboomer.dev
- Dashboard: https://dash.socialcutter.theboomer.dev