# Process images with the SocialCutter API from Python
> SocialCutter API client in Python with requests: health, platforms, processing by URL and file, batches, history, wallet and retries with backoff.
- URL: https://socialcutter.theboomer.dev/en/guides/python/
- Idioma: en
- Familia: api
- Actualizado: 2026-09-24
- Palabras clave: Python, requests, API, SocialCutter, process images, multipart, API key
## Requirements

Install `requests` and keep the credentials in environment variables:

```bash
pip install requests
export SOCIALCUTTER_API_URL="https://api.socialcutter.theboomer.dev"
export SOCIALCUTTER_API_KEY="sc_your_key"
```

Create the key at https://dash.socialcutter.theboomer.dev under **Profile → API keys**. It starts with `sc_` and is shown only once. The full API reference is at https://docs.socialcutter.theboomer.dev.

## Minimal client

A reusable session avoids repeating the header and credentials on every call:

```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"]  # never hardcode it

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()` raises on any non-2xx response, so error bodies are never processed as if they were valid.

## Health and credentials

```python
# Public: no key required
print(get("/api/v1/health"))          # status, version, uptime, database

# Identity of the authenticated account
print(get("/api/v1/auth/me"))

# Available uses
print(get("/api/v1/credits"))
```

`/api/v1/health` returns the service state and database connection. If `/api/v1/auth/me` returns 401, the key is missing, malformed or revoked.

## List platforms and formats

```python
data = get("/api/v1/platforms")
for platform, formats in data["platforms"].items():
    for f in formats:
        print(platform, f["format"], f"{f['width']}x{f['height']}", f["aspect_ratio"])
```

`/platforms`, `/formats` and `/fit-modes` are public. Use them to build the destination list without hardcoding dimensions.

## Process an image by URL

```python
resp = post_json("/api/v1/images/process", {
    "source": {"type": "url", "value": "https://example.com/photo.jpg"},
    "destinations": [
        {"platform": "instagram", "format": "post"},
        {"platform": "tiktok", "format": "cover"},
    ],
})
print(resp["id"], resp["status"])
```

## Process a local file (multipart)

```python
with open("photo.jpg", "rb") as fh:
    r = session.post(
        f"{API_URL}/api/v1/images/process/upload",
        files={"file": ("photo.jpg", fh, "image/jpeg")},
        data={"destinations": '[{"platform":"linkedin","format":"post"}]'},
        timeout=60,
    )
    r.raise_for_status()
    resp = r.json()
```

In multipart the file goes in the `file` field and `destinations` is a JSON string in a form field. The upload limit is 5 MB; above that the API returns 413.

## Process a batch

```python
batch = 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"}]},
    ]
})
```

## Read the response and download

The response carries the job id in `id`, a `status` and an `outputs` list, one entry per destination with `platform`, `format`, `url`, `width`, `height` and `size_bytes`. Timings live in `metadata`.

```python
resp = post_json("/api/v1/images/process", {
    "source": {"type": "url", "value": "https://example.com/photo.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)
```

## History and wallet

```python
# Last 10 images
print(get("/api/v1/history", limit=10))

# Only those created from the API
print(get("/api/v1/history", origin="api", limit=10))

# Daily quota, extra pool and purchased balance
print(get("/api/v1/wallet"))
```

`limit` accepts 1 to 100 and `skip` pages through results. The `origin` filter separates `browser` (dashboard) from `api`.

## Errors and retries with backoff

Capture the status code and decide whether a retry is worth it. Client errors (4xx, except 429) are not retried: repeating the same request will not fix them.

```python
import time

def with_retries(fn, attempts=4, base=0.5):
    for i in range(attempts):
        try:
            return fn()
        except requests.HTTPError as e:
            code = e.response.status_code
            if code == 429 or code >= 500:
                if i == attempts - 1:
                    raise
                time.sleep(base * (2 ** i))   # 0.5, 1, 2, 4 s
                continue
            raise

with_retries(lambda: post_json("/api/v1/images/process", payload))
```

For automatic retries across the whole session, `requests` delegates to `urllib3.Retry`, which applies exponential wait and honors the `Retry-After` header:

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

Documented pattern at https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry. Names and options can change between library versions; confirm them in that documentation.

## Cost

- 1 use per destination (platform and format) per request.
- Repeated destinations in the same request are not charged twice.
- Failed processings are refunded.

## Typical errors

| Code | Meaning |
|---|---|
| 400 | Invalid payload: unknown platform, format or mode, malformed JSON, or a key already active |
| 401 | Missing, malformed, expired or revoked credentials |
| 404 | Resource not found (image or file id) |
| 413 | The file exceeds 5 MB |
| 422 | Request validation error |
| 429 | Wallet quota exhausted |
| 500 | Processing failure; uses from that request are refunded |

## Next steps

- Terminal guide: [Process images with the API from the terminal (curl)](/en/guides/curl/)
- Node guide: [Process images with the SocialCutter API from Node.js](/en/guides/node/)
- PHP guide: [Process images with the SocialCutter API from PHP](/en/guides/php/)
- API documentation: https://docs.socialcutter.theboomer.dev
- Dashboard: https://dash.socialcutter.theboomer.dev