# Dropbox a SocialCutter: entrada y salida de imágenes
> Lee la imagen nueva de una carpeta de Dropbox con files/list_folder y su cursor, procésala con SocialCutter y sube cada formato a otra carpeta con files/upload.
- URL: https://socialcutter.theboomer.dev/guias/dropbox/
- Idioma: es
- Familia: ia
- Actualizado: 2026-09-24
- Palabras clave: Dropbox, API v2, files/list_folder, cursor, files/upload, Python, recorte centrado
## Un flujo de entrada y una carpeta de salida

El patrón se repite: alguien deja el diseño en una carpeta compartida de Dropbox y de ahí tienen que salir las versiones de cada red. SocialCutter no entra en tu Dropbox ni lo vigila: recibe una imagen y una lista de destinos y devuelve una URL por salida. Mover los ficheros es cosa de tu script.

El circuito tiene cuatro pasos:

1. Detectar el fichero nuevo en la carpeta de entrada.
2. Traer el binario (o un enlace temporal).
3. Procesarlo con SocialCutter.
4. Subir cada salida a la carpeta de destino.

Si prefieres no programar, el mismo circuito se monta con nodos: [Automatiza el recorte de imágenes con n8n](/guias/n8n/) o [con Make](/guias/make/).

## Por qué pre-generar antes de subir

Dropbox guarda y sincroniza el fichero tal cual, no recorta. Si subes un solo maestro y lo reutilizas para todos los huecos, cada destino lo estirará o lo recortará a su manera. Generar las medidas por adelantado te da ficheros con la medida exacta y el recorte **centrado** decidido por ti.

| Hueco donde va la imagen | Destino SocialCutter | Medida |
|---|---|---|
| Cuadrado para una ficha o una miniatura | `instagram` `post` | 1080x1080 (1:1) |
| Vertical para un story de la carpeta | `instagram` `story` | 1080x1920 (9:16) |
| Apaisada estándar para una ficha | `twitter` `post` | 1200x675 (16:9) |
| Apaisada ancha para un banner | `linkedin` `post` | 1200x627 (1.91:1) |
| Cabecera estrecha de la carpeta | `linkedin` `cover` | 1128x191 (5.9:1) |

Los destinos y las medidas salen del catálogo público: `GET /api/v1/platforms` devuelve 6 plataformas y 13 destinos con su anchura, altura y proporción.

## Listar la carpeta con files/list_folder

`files/list_folder` es un endpoint RPC: los argumentos van en el cuerpo JSON y la respuesta también es JSON.

```
POST https://api.dropboxapi.com/2/files/list_folder
Scope: files.metadata.read
```

El cuerpo acepta, entre otros, `path` (la carpeta; la cadena vacía es la raíz), `recursive`, `include_deleted`, `limit` (aproximado, hasta 2000 entradas) e `include_non_downloadable_files`.

La respuesta es un `ListFolderResult` con tres campos que importan:

- `entries`: los ficheros y subcarpetas. Cada entrada trae `name`, `path_lower`, `path_display` y un `.tag` que distingue `file`, `folder` y `deleted`.
- `cursor`: el testigo de paginación.
- `has_more`: si es verdadero, quedan entradas.

Cuando `has_more` es verdadero se sigue con el cursor:

```
POST https://api.dropboxapi.com/2/files/list_folder/continue
Scope: files.metadata.read
```

Ese endpoint recibe `{"cursor": "..."}` y devuelve otro `ListFolderResult`. El mismo cursor sirve para dos cosas: terminar la paginación de una carpeta grande y, en la siguiente vuelta del proceso, pedir **solo los cambios desde la última consulta**. Guárdalo entre ejecuciones y no vuelvas a recorrer la carpeta entera.

Dos avisos de la documentación oficial que ahorran depuraciones: si el cursor se invalida, la respuesta trae el error `reset` y hay que empezar de nuevo con `files/list_folder`; y si dos llamadas idénticas a `list_folder` coinciden en el tiempo, Dropbox puede responder un error de límite de peticiones, así que el reintento debe esperar a que termine la anterior.

## Traer el binario con files/download

`files/download` es un endpoint de contenido: va por otro dominio y los argumentos viajan en la cabecera `Dropbox-API-Arg` (JSON serializado, con los caracteres no ASCII escapados), no en el cuerpo.

```
POST https://content.dropboxapi.com/2/files/download
Dropbox-API-Arg: {"path": "/Disenos/entrada/maestro.jpg"}
Scope: files.content.read
```

El cuerpo de la respuesta **es el fichero**, y los metadatos llegan en la cabecera `Dropbox-API-Result`. Es el camino cuando quieres que el binario viaje dentro de tu proceso, sin exponer ningún enlace.

`files/download` solo funciona con ficheros descargables: los documentos que Dropbox guarda como enlace externo hay que exportarlos antes. Trabaja con raster (JPG, PNG, WebP); si la carpeta es de documentos, no es tu caso.

## Dos caminos para pasar la imagen a SocialCutter

**Multipart, sin enlaces.** El binario se envía a `POST /api/v1/images/process/upload` con la cabecera `X-API-Key`, el fichero en el campo `file` y la lista de destinos en el campo `destinations` como cadena JSON. El techo es 5 MB; por encima responde 413.

**Por URL con enlace temporal.** `files/get_temporary_link` es un RPC que recibe `{"path": "..."}` y devuelve `link` y `metadata`. Ese enlace caduca a las cuatro horas y después responde 410 Gone, así que se pide justo antes de la llamada y se pasa como `source` de tipo `url`:

```json
{ "source": { "type": "url", "value": "<link temporal>" },
  "destinations": [ { "platform": "instagram", "format": "post" } ] }
```

Es la vía más cómoda cuando quieres que el fichero no pase dos veces por tu proceso, pero el enlace queda expuesto durante esas cuatro horas.

## Subir las salidas a otra carpeta

`files/upload` vuelve a ser un endpoint de contenido: el binario va en el cuerpo con `Content-Type: application/octet-stream` y los argumentos en `Dropbox-API-Arg`, que en este caso es un `CommitInfo`.

```json
{ "path": "/Disenos/salida/maestro-instagram-post.webp",
  "mode": "add",
  "autorename": true,
  "mute": true }
```

- `path`: ruta de destino. Debe empezar por barra.
- `mode`: `add` (por defecto, falla si ya existe), `overwrite`, o `update` con la revisión del fichero.
- `autorename`: si hay conflicto, Dropbox renombra en lugar de fallar. Útil en carpetas compartidas donde alguien puede haber dejado un fichero con el mismo nombre.
- `mute`: no notifica a los clientes de escritorio. Recomendable en procesos desatendidos que escriben muchos ficheros.
- `strict_conflict`: endurece cómo se comparan los conflictos.

No se debe usar este endpoint para ficheros de más de 150 MB; por encima hay que montar una sesión con `upload_session/start`. Las salidas de SocialCutter son imágenes de pocos cientos de kilobytes, así que no es un límite que te vaya a tocar.

Un nombre de destino útil conserva el original y añade la plataforma y el formato:

```
maestro-instagram-post.webp
maestro-twitter-post.webp
maestro-linkedin-post.webp
```

## Requisitos de OAuth y de permisos

- **Scopes.** La app de la App Console se declara con permisos en la pestaña `Permissions` y quedan fijados en el token:
  - `files.metadata.read` — listar la carpeta y seguir el cursor.
  - `files.content.read` — descargar y pedir el enlace temporal.
  - `files.content.write` — subir las salidas.
- **App Folder o Full Dropbox.** Si la app solo toca su propia carpeta `/apps`, con el acceso App Folder es suficiente. Para leer y escribir en una carpeta que ya existe en la cuenta (el caso de esta guía) hay que elegir Full Dropbox.
- **Token de larga duración.** Para procesos en segundo plano, cuando no hay nadie delante, conviene pedir el token con `token_access_type=offline`: así la respuesta del endpoint de token trae un `refresh_token` con el que renovar el token corto sin volver a autorizar.
- **Reautorización.** Si el usuario revoca el acceso de la app desde su cuenta, las llamadas empiezan a devolver 401 y hay que volver a autorizar. Los scopes se pueden ampliar después con el parámetro `scopes` de la URL de autorización.
- **Límite de subida a SocialCutter.** 5 MB por imagen; por encima responde 413.

## Snippet completo en Python

```python
import json, os, requests

API = "https://api.socialcutter.theboomer.dev"
RPC = "https://api.dropboxapi.com/2"
CONTENIDO = "https://content.dropboxapi.com/2"
ENTRADA = "/Disenos/entrada"
SALIDA = "/Disenos/salida"
DESTINOS = [
    {"platform": "instagram", "format": "post"},
    {"platform": "twitter", "format": "post"},
]

dbx = requests.Session()
dbx.headers["Authorization"] = f"Bearer {os.environ['DROPBOX_TOKEN']}"

sc = requests.Session()
sc.headers["X-API-Key"] = os.environ["SOCIALCUTTER_API_KEY"]

def listar(path, cursor=None):
    if cursor is None:
        url, body = f"{RPC}/files/list_folder", {"path": path}
    else:
        url, body = f"{RPC}/files/list_folder/continue", {"cursor": cursor}
    r = dbx.post(url, json=body, timeout=60)
    r.raise_for_status()
    return r.json()

def descargar(path):
    # Los argumentos viajan en la cabecera, no en el cuerpo
    r = dbx.post(f"{CONTENIDO}/files/download",
                 headers={"Dropbox-API-Arg": json.dumps({"path": path})},
                 timeout=120)
    r.raise_for_status()
    return r.content          # el binario; los metadatos, en Dropbox-API-Result

def subir(path, data):
    r = dbx.post(f"{CONTENIDO}/files/upload",
                 headers={"Dropbox-API-Arg": json.dumps({
                     "path": path, "mode": "add",
                     "autorename": True, "mute": True}),
                     "Content-Type": "application/octet-stream"},
                 data=data, timeout=120)
    r.raise_for_status()
    return r.json()

def enlace_temporal(path):
    r = dbx.post(f"{RPC}/files/get_temporary_link",
                 json={"path": path}, timeout=60)
    r.raise_for_status()
    return r.json()["link"]   # caduca a las cuatro horas

resultado = listar(ENTRADA)
while True:
    for entrada in resultado["entries"]:
        if entrada[".tag"] != "file" or not entrada["name"].lower().endswith(".jpg"):
            continue

        binario = descargar(entrada["path_lower"])
        r = sc.post(f"{API}/api/v1/images/process/upload",
                    headers={"X-API-Key": os.environ["SOCIALCUTTER_API_KEY"]},
                    files={"file": (entrada["name"], binario, "image/jpeg")},
                    data={"destinations": json.dumps(DESTINOS)}, timeout=120)
        r.raise_for_status()

        for salida in r.json()["outputs"]:
            img = sc.get(salida["url"], timeout=120)
            img.raise_for_status()
            destino = f"{SALIDA}/{entrada['name']}-{salida['platform']}-{salida['format']}.webp"
            print(subir(destino, img.content)["path_display"])

    if not resultado["has_more"]:
        break
    resultado = listar(ENTRADA, cursor=resultado["cursor"])
```

Guarda el `cursor` de la última vuelta junto al estado del proceso: la siguiente ejecución puede retomar desde ahí en lugar de volver a mirar toda la carpeta.

## Alternativa con enlace temporal y curl

```bash
# 1. Enlace temporal del maestro (caduca en 4 horas)
LINK=$(curl -s -X POST "https://api.dropboxapi.com/2/files/get_temporary_link" \
  -H "Authorization: Bearer $DROPBOX_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"path":"/Disenos/entrada/maestro.jpg"}' | jq -r .link)

# 2. Genera los formatos
curl -s -X POST "https://api.socialcutter.theboomer.dev/api/v1/images/process" \
  -H "X-API-Key: $SOCIALCUTTER_API_KEY" \
  -H "Content-Type: application/json" \
  --data "{\"source\":{\"type\":\"url\",\"value\":\"$LINK\"},\"destinations\":[{\"platform\":\"instagram\",\"format\":\"post\"}]}" \
  > salida.json

jq -r '.outputs[] | .platform + " " + .format + " " + .url' salida.json

# 3. Sube la primera salida a la carpeta de destino
URL=$(jq -r '.outputs[0].url' salida.json)
NOMBRE=$(jq -r '"\(.outputs[0].platform)-\(.outputs[0].format).webp"' salida.json)
curl -s -X POST "https://content.dropboxapi.com/2/files/upload" \
  -H "Authorization: Bearer $DROPBOX_TOKEN" \
  -H "Content-Type: application/octet-stream" \
  -H "Dropbox-API-Arg: {\"path\":\"/Disenos/salida/$NOMBRE\",\"mode\":\"add\",\"autorename\":true}" \
  --data-binary @"salida.webp" | jq -r '.path_display, .size'
```

Si vas a subir varios ficheros seguidos, recuerda que las llamadas de escritura compiten entre sí: espácialas o agrupa la subida de los ficheros de una misma imagen.

## Coste

- **1 uso por destino** (plataforma y formato) por petición a SocialCutter. Destinos repetidos no se cobran dos veces y los procesamientos fallidos se devuelven.
- Planes: 0 EUR (3 usos/día), 3 EUR (10/día), 9 EUR (30/día) y 29 EUR (100/día), todos con API y servidor MCP incluidos.
- La API de Dropbox no se cobra por llamada para apps normales, pero en equipos de Dropbox Business las subidas cuentan en el límite mensual de llamadas de transporte de datos.

## Errores típicos

| Código | Origen | Qué pasa | Qué hacer |
|---|---|---|---|
| 400 | Dropbox | Cuerpo o cabecera mal formados, o JSON fuera de validación | Revisar el payload; reintentar no lo arregla |
| 401 | Dropbox | Token caducado, revocado o sin permisos suficientes | Refrescar el token con el `refresh_token` o volver a autorizar |
| 403 | Dropbox | La cuenta o el equipo no tiene acceso a esa llamada o a ese recurso | Revisar el scope y la ruta; la app puede estar en App Folder y no ver la carpeta |
| 409 | Dropbox | Error específico del endpoint: el detalle va en `error` y `error_summary` | Es el caso de `path_not_found`: alguien ha movido o borrado el fichero |
| 429 | Dropbox | Demasiadas llamadas o demasiadas escrituras simultáneas | Esperar lo que indique `Retry-After` o aplicar espera exponencial |
| 500 | Dropbox | Error interno, suele ser breve | Reintentar con espera, no en bucle rápido |
| `reset` | Dropbox | Cursor invalidado | Empezar de nuevo con `files/list_folder` y guardar el cursor nuevo |
| 410 Gone | Enlace temporal | Han pasado más de cuatro horas desde que se pidió | Volver a llamar a `files/get_temporary_link` justo antes de usarlo |
| 401 | SocialCutter | Falta la cabecera `X-API-Key` o la clave no vale | Comprobar que el valor empieza por `sc_` y sigue activo |
| 413 | SocialCutter | El maestro supera 5 MB | Reducir la imagen antes de enviarla |
| 429 | SocialCutter | Cuota del monedero agotada | Consultar `GET /api/v1/wallet` antes de lotes grandes |

## Lo que SocialCutter no hace

El recorte es **centrado y determinista**: escala la imagen y recorta el exceso por igual a los dos lados. No analiza el contenido de la imagen para decidir qué conservar, no edita la foto (no retoca color, no quita fondos, no compone texto), no publica en redes sociales y no acepta ficheros de más de 5 MB. Genera las versiones con la medida exacta de cada destino y devuelve sus URLs: el intercambio con Dropbox y la publicación son de tu script.

## Siguientes pasos

- El mismo circuito con Drive: [Procesa imágenes de Google Drive con SocialCutter](/guias/google-drive/)
- Código: [Automatiza SocialCutter con Python](/guias/python/) y [desde la terminal con curl](/guias/curl/)
- Sin código: [Automatiza el recorte de imágenes con n8n](/guias/n8n/), [con Make](/guias/make/) y [con Zapier](/guias/zapier/)
- Panorama: [Automatizar imágenes para redes sociales: los 4 caminos](/guias/automatizar-imagenes-redes-sociales/)
- Documentación de la API de SocialCutter: https://docs.socialcutter.theboomer.dev
- Referencia HTTP de Dropbox: https://www.dropbox.com/developers/documentation/http/documentation