← Code Recipes
Image & Video

Stability AI Free code examples in Python and JavaScript

Stable Diffusion image generation, upscaling and background removal.

C# Python Java JavaScript

WHAT THIS PAGE COVERS

Image-to-image, where the strength parameter decides everything.

Model sd3.5-large
Package requests
Checked

Model ids change. If a sample returns a 404 on the model, that is the first thing to check against the official docs.

Install

No official Python SDK — Stability documents the REST API, and requests is enough. That is the more durable sample anyway.

pip install requests

export STABILITY_API_KEY="sk-your-key"   # macOS / Linux
setx STABILITY_API_KEY "sk-your-key"     # Windows, then open a new shell
Generate an image

files={'none': ''} IS LOAD-BEARING. Without it requests sends form-urlencoded and the API returns a validation error that says nothing about encoding. The accept header decides bytes versus base64.

import os
import requests

response = requests.post(
    "https://api.stability.ai/v2beta/stable-image/generate/sd3",
    headers={
        "authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
        "accept": "image/*",       # application/json would give base64
    },
    files={"none": ""},            # forces multipart encoding
    data={
        "prompt": "A studio photograph of a brass microphone on a dark desk",
        "model": "sd3.5-large",
        "output_format": "png",
    },
    timeout=120,
)

response.raise_for_status()

with open("microphone.png", "wb") as handle:
    handle.write(response.content)
Image to image, where strength decides everything

STRENGTH IS THE WHOLE PARAMETER. Below about 0.3 the output is the input with a filter on it; above about 0.8 the input may as well not have been sent. The useful range is narrow and worth sweeping before tuning the prompt.

with open("sketch.png", "rb") as source:
    response = requests.post(
        "https://api.stability.ai/v2beta/stable-image/generate/sd3",
        headers={
            "authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
            "accept": "image/*",
        },
        files={"image": source},
        data={
            "prompt": "A brass microphone, studio lighting, photographic",
            "model": "sd3.5-large",
            "mode": "image-to-image",   # required when image is present
            "strength": 0.55,           # 0 keeps the input, 1 ignores it
            "output_format": "png",
        },
        timeout=120,
    )

response.raise_for_status()
open("rendered.png", "wb").write(response.content)
Sweeping strength instead of guessing

Five requests and a contact sheet answer the question faster than an afternoon of single tries. Fix the seed, or you are comparing strength AND noise at the same time.

for strength in (0.35, 0.45, 0.55, 0.65, 0.75):
    with open("sketch.png", "rb") as source:
        response = requests.post(
            "https://api.stability.ai/v2beta/stable-image/generate/sd3",
            headers={
                "authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
                "accept": "image/*",
            },
            files={"image": source},
            data={
                "prompt": "A brass microphone, studio lighting, photographic",
                "model": "sd3.5-large",
                "mode": "image-to-image",
                "strength": strength,
                "seed": 42,          # hold it fixed or this proves nothing
                "output_format": "png",
            },
            timeout=120,
        )

    response.raise_for_status()
    open(f"strength-{strength}.png", "wb").write(response.content)
Removing a background

A different route on the same API and the cheapest thing here. No prompt at all — it is a segmentation model, so the only useful knob is the output format.

with open("product.png", "rb") as source:
    response = requests.post(
        "https://api.stability.ai/v2beta/stable-image/edit/remove-background",
        headers={
            "authorization": f"Bearer {os.environ['STABILITY_API_KEY']}",
            "accept": "image/*",
        },
        files={"image": source},
        data={"output_format": "png"},   # png keeps the alpha channel
        timeout=120,
    )

response.raise_for_status()
open("cutout.png", "wb").write(response.content)
Install

Nothing to install on modern Node — fetch, FormData and Blob are all built in. Server-side only.

# Node 18+ has fetch, FormData and Blob built in.
export STABILITY_API_KEY="sk-your-key"
Generate an image

DO NOT SET Content-Type. fetch computes the multipart boundary from the FormData, and setting the header by hand omits it — the request then fails with an error about a missing prompt, which is a lie.

import { writeFile } from "node:fs/promises";

const form = new FormData();
form.set("prompt", "A studio photograph of a brass microphone on a dark desk");
form.set("model", "sd3.5-large");
form.set("output_format", "png");

const response = await fetch(
  "https://api.stability.ai/v2beta/stable-image/generate/sd3",
  {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.STABILITY_API_KEY}`,
      accept: "image/*",
      // No content-type — fetch sets it, with the boundary.
    },
    body: form,
  },
);

if (!response.ok) throw new Error(await response.text());

await writeFile("microphone.png", Buffer.from(await response.arrayBuffer()));
Image to image, where strength decides everything

STRENGTH IS THE WHOLE PARAMETER, and mode must be set the moment an image is attached. Leaving mode out with a file present is a 400 that names neither.

import { readFile } from "node:fs/promises";

const sketch = await readFile("sketch.png");

const form = new FormData();
form.set("image", new Blob([sketch], { type: "image/png" }), "sketch.png");
form.set("prompt", "A brass microphone, studio lighting, photographic");
form.set("model", "sd3.5-large");
form.set("mode", "image-to-image");   // required once image is present
form.set("strength", "0.55");         // 0 keeps the input, 1 ignores it
form.set("output_format", "png");

const response = await fetch(
  "https://api.stability.ai/v2beta/stable-image/generate/sd3",
  {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.STABILITY_API_KEY}`,
      accept: "image/*",
    },
    body: form,
  },
);

if (!response.ok) throw new Error(await response.text());
await writeFile("rendered.png", Buffer.from(await response.arrayBuffer()));
Sweeping strength instead of guessing

Sequential, not Promise.all: Stability rate-limits and a 429 costs a 60-second timeout, which is slower than just doing them in order.

const sketch = await readFile("sketch.png");

for (const strength of [0.35, 0.45, 0.55, 0.65, 0.75]) {
  const form = new FormData();
  form.set("image", new Blob([sketch], { type: "image/png" }), "sketch.png");
  form.set("prompt", "A brass microphone, studio lighting, photographic");
  form.set("model", "sd3.5-large");
  form.set("mode", "image-to-image");
  form.set("strength", String(strength));
  form.set("seed", "42");             // hold it fixed or this proves nothing
  form.set("output_format", "png");

  const response = await fetch(
    "https://api.stability.ai/v2beta/stable-image/generate/sd3",
    {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.STABILITY_API_KEY}`,
        accept: "image/*",
      },
      body: form,
    },
  );

  if (!response.ok) throw new Error(await response.text());
  await writeFile(`strength-${strength}.png`,
    Buffer.from(await response.arrayBuffer()));
}
Reading a failure

With accept: image/* a success is bytes and a failure is JSON. Calling .arrayBuffer() on the failure gives you an unreadable buffer instead of the message, so branch on response.ok first.

async function generate(form) {
  const response = await fetch(
    "https://api.stability.ai/v2beta/stable-image/generate/sd3",
    {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.STABILITY_API_KEY}`,
        accept: "image/*",
      },
      body: form,
    },
  );

  if (response.ok) return Buffer.from(await response.arrayBuffer());

  // Errors come back as JSON whatever the accept header says.
  const detail = await response.json().catch(() => ({}));

  // 403 is content moderation, not auth — retrying will not help.
  // 429 is rate limiting and carries a 60-second cool-off.
  throw new Error(`Stability ${response.status}: ${JSON.stringify(detail)}`);
}

QUESTIONS

Which languages are the Stability AI examples in?

Python and JavaScript. Every sample runs as pasted and reads its credentials from an environment variable — no key is ever written into the code.

Which package do I install for Stability AI?

The samples use requests. The install step on each language tab gives the exact command.

Which Stability AI model do these examples use?

sd3.5-large, checked against the official documentation on 26 September 2026. Model ids change often; if a call returns a model-not-found error, check the current list in the official docs.

Where is the official Stability AI documentation?

https://platform.stability.ai/docs/api-reference — it is linked at the bottom of this page, and it is the authority whenever it disagrees with a sample here.

ALSO IN IMAGE & VIDEO

The authority

Official Stability AI documentation

Model names, pricing and rate limits change faster than any page here can. When this page and the official docs disagree, the official docs are right.

Read the docs ↗

https://platform.stability.ai/docs/api-reference

New drops, every week

Free tools & Claude templates, landing regularly.

We keep shipping new free tools, code recipes and Claude Code templates. Follow @freeyantra on Instagram or join the newsletter so you never miss one.

Follow @freeyantra

or get them by email

Double opt-in. Unsubscribe any time. Privacy