Skip to main content
SocialCutter

API and development

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.

  • Python
  • requests
  • API
  • SocialCutter
  • process images
  • multipart
  • API key

Requirements

Install requests and keep the credentials in environment variables:

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:

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

# 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

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

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)

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

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.

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

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

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:

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

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

Next steps

Frequently asked questions

Which library do I need in Python?

requests, installed with pip install requests. For multipart you need nothing else: requests builds the form when you pass files and data.

Where do I put the API key?

In the SOCIALCUTTER_API_KEY environment variable, never in code. It travels on every request in the X-API-Key header.

How do I upload an image from disk?

With POST /api/v1/images/process/upload using files={'file': ...} and destinations as a JSON form field. The limit is 5 MB.

How is a batch charged?

1 use per destination (platform and format) per image. A batch of 3 images with 2 destinations each is 6 uses.

Why do I get 429?

Because the wallet quota is exhausted. Check GET /api/v1/credits and GET /api/v1/wallet before large batches.