EN ZalogujOtwórz Studio
Wersja podglądowa. Katalog modeli, ceny, kalkulator i dokumentacja działają. Generowanie uruchomimy po starcie serwisu.

Obrazy AI ze skryptu w Pythonie - od jednego pliku do serii

Generowanie obrazów przez API w Pythonie: gotowy kod dla GPT Image 2.5, Nano Banana Pro i FLUX.2 pro, edycja zdjęć, seria równoległa, błędy i koszt.

Obraz z modelu AI dostaniesz w Pythonie jednym wywołaniem requests.post. Podajesz identyfikator modelu (gpt-image-2.5, nano-banana-pro, flux-2-pro), prompt i format, a parametr ?wait=60 sprawia, że link do gotowego pliku wraca w tej samej odpowiedzi. Na Kadraris API pokazuję całą ścieżkę: pierwszy obraz, funkcję z zapisem na dysk, edycję zdjęcia, serię w kilku wątkach i obsługę błędów. Kod nadaje się do skopiowania.

Trzy modele i ich charaktery

Różnią się nie tyle jakością, co tym, w czym są najlepsze:

ModelZa obrazW czym błyszczyDo czego go biorę
GPT Image 2.50,293 zł za obrazwierność poleceniom przy kolejnych poprawkachedycje zdjęć, grafiki według instrukcji
Nano Banana Prood 0,411 zł za obraznapisy, wiele referencji, ta sama postaćplakaty, infografiki, seria z jedną osobą
FLUX.2 [pro]od 0,403 zł za obrazfotorealizm, faktury materiałówprodukty, wnętrza, portrety

Co mówią producenci: GPT Image 2.5 (OpenAI, wrzesień 2026) bierze do 16 referencji i generuje do 4K. Nano Banana Pro, czyli Gemini 3 Pro Image, łączy do 14 obrazów wejściowych i według Google utrzymuje wygląd do 5 osób. FLUX.2 pro od Black Forest Labs tworzy i edytuje do 4 megapikseli, z kilkoma referencjami naraz.

Limity w Kadrarisie odczytasz ze schematu modelu - bez klucza:

bash
curl https://kadraris.com/v1/models/nano-banana-pro

W odpowiedzi szukaj pola input_schema: lista przyjmowanych parametrów i ich zakresów.

Przygotowanie środowiska

  • Python 3.10+ i pip install requests.
  • Konto w Kadrarisie: rejestracja, doładowanie od 40 zł (BLIK lub karta), klucz sk-kd-... z panelu.
  • Do edycji: miejsce na pliki z publicznym adresem HTTPS (bucket, własny serwer, CDN).
  • Klucz trzymaj w zmiennej środowiskowej, nigdy w kodzie:
bash
export KADRARIS_API_KEY="sk-kd-..."

W PowerShellu to $env:KADRARIS_API_KEY = "sk-kd-...".

Pierwsze wywołanie

Obraz powstaje zwykle w kilkanaście do kilkudziesięciu sekund, dlatego dodaję ?wait=60. Serwer wstrzymuje odpowiedź do końca generacji. Jeśli nie zdąży, zwraca 202 i wtedy odpytujesz zadanie po id.

python
import os
import requests

API = "https://kadraris.com/v1"
KEY = os.environ["KADRARIS_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

r = requests.post(
    f"{API}/generate",
    params={"wait": 60},
    headers=HEADERS,
    json={
        "model": "gpt-image-2.5",
        "input": {
            "prompt": "Glass teapot on a wooden table, "
                      "green tea leaves, side window light, "
                      "product photo",
            # GPT Image 2.5: wymiar w px albo "auto"
            "aspect_ratio": "1536x1024",
        },
    },
    timeout=130,  # musi przekraczać wait
)
task = r.json()
print(r.status_code, task["status"])

if task["status"] == "succeeded":
    print(task["output"][0]["url"], task["cost"])

Dwie pułapki. Po pierwsze timeout po stronie klienta musi być dłuższy niż wait, inaczej requests przerwie połączenie przed odpowiedzią. Po drugie zakończone zadanie może mieć status failed - wtedy nic nie płacisz, blokada wraca na saldo, ale pliku nie ma.

Funkcja, której używam w projektach

W prawdziwym skrypcie zamykam to w jednej funkcji. Obsługuje generację, dopytywanie, gdy minuta nie wystarczy, i zapis na dysk:

python
import time
import pathlib


def wait_for_task(task_id, timeout=300):
    end = time.time() + timeout
    while time.time() < end:
        task = requests.get(f"{API}/tasks/{task_id}",
                            headers=HEADERS, timeout=30).json()
        if task["status"] in ("succeeded", "failed"):
            return task
        time.sleep(3)
    raise TimeoutError(task_id)


def generate_image(model, prompt, folder="obrazy",
                   **params):
    r = requests.post(
        f"{API}/generate",
        params={"wait": 60},
        headers=HEADERS,
        json={"model": model,
              "input": {"prompt": prompt, **params}},
        timeout=130,
    )
    if r.status_code not in (200, 202):
        err = r.json()["error"]
        raise RuntimeError(
            f"{r.status_code} {err['type']}: {err['message']}")

    task = r.json()
    if task["status"] not in ("succeeded", "failed"):
        task = wait_for_task(task["id"])
    if task["status"] == "failed":
        raise RuntimeError(
            f"porażka, 0 zł: {task['error']['message']}")

    out = pathlib.Path(folder)
    out.mkdir(exist_ok=True)
    saved = []
    for i, item in enumerate(task["output"]):
        p = out / f"{model}_{task['id']}_{i}.png"
        p.write_bytes(
            requests.get(item["url"], timeout=60).content)
        saved.append(p)
    return saved


print(generate_image(
    "nano-banana-2",
    "Small home office, plants, white desk, rainy daylight",
    aspect_ratio="16:9", num_images=2))

Pliki zapisuję od razu, bo linki w output działają przez ograniczony czas. Końcówka .png to uproszczenie - jeśli format ma znaczenie, odczytaj go z nagłówka Content-Type przy pobieraniu.

Ustawienia, które zmieniają wynik

Wspólne dla modeli obrazu są tylko dwa pola:

PoleCo podajeszPo co
prompttekstopis obrazu albo polecenie zmiany
image_urlslista adresów HTTPSedycja lub referencje (limit zależy od modelu)

Reszta różni się między modelami:

  • aspect_ratio - we FLUX.2 pro i Nano Banana Pro to proporcja typu 4:3, w GPT Image 2.5 wymiar w pikselach (1024x1024, 1536x1024, 1024x1536) albo auto.
  • num_images (1-4) - mają go m.in. Nano Banana 2 i Wan 2.7 Image. Trzy modele z tabeli zwracają jeden obraz na zadanie, więc warianty to osobne zadania. Koszt rośnie razem z liczbą obrazów.
  • seed - jest m.in. w FLUX.2 [max], FLUX.1 Kontext [pro] i Wan 2.7 Image, w trzech głównych modelach go nie ma. Ten sam seed z tym samym promptem daje zbliżony obraz, nie zawsze identyczny co do piksela. Używam go, gdy zmieniam jedno słowo i chcę zobaczyć tylko jego wpływ.
  • negative_prompt, resolution, quality - tylko w części modeli.

Pewną listę daje GET /v1/models/{id}. Pole spoza schematu albo zła wartość kończy się kodem 422, zanim cokolwiek zostanie pobrane z salda. Przy szukaniu kompozycji robię 2-4 warianty, wybieram jeden i dalej już go edytuję, zamiast losować od zera.

Zmiana istniejącego zdjęcia

Edycja to ten sam endpoint z dodanym image_urls. Plik musi leżeć pod publicznym adresem HTTPS.

python
files = generate_image(
    "gpt-image-2.5",
    "Put the backpack on a clean light-grey studio "
    "background. Do not change the backpack, its straps, "
    "its logo or its shadow.",
    image_urls=["https://twoja-domena.pl/pliki/plecak.jpg"],
)

W prompcie zawsze wymieniam, czego nie ruszać. Bez tego model potrafi "przy okazji" poprawić kolor produktu czy kształt detalu. GPT Image 2.5 najlepiej słucha takich zastrzeżeń, więc do zdjęć produktowych sięgam po niego najczęściej.

Kilka referencji naraz, np. postać z jednego zdjęcia w miejscu z drugiego:

python
files = generate_image(
    "nano-banana-pro",
    "The man from the first image stands on the train "
    "platform from the second image, holding a coffee, "
    "same jacket, foggy morning",
    image_urls=[
        "https://twoja-domena.pl/pliki/osoba.jpg",
        "https://twoja-domena.pl/pliki/peron.jpg",
    ],
    aspect_ratio="9:16",
)

Więcej edycji z polskimi poleceniami znajdziesz w poradniku Nano Banana Pro po polsku.

Wiele obrazów naraz

Gdy potrzebuję kilkudziesięciu ilustracji do wpisów albo serii banerów, nie czekam na każdą po kolei. Puszczam kilka zadań równolegle:

python
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed

PROMPTS = [
    "Flat illustration of a man repairing a bike, warm tones",
    "Flat illustration of kids building a sandcastle, warm tones",
    "Flat illustration of a baker opening a shop, warm tones",
    "Flat illustration of a dog walk in the park, warm tones",
]

with ThreadPoolExecutor(max_workers=4) as pool:
    jobs = {pool.submit(generate_image, "flux-2-pro", p,
                        aspect_ratio="16:9"): p
            for p in PROMPTS}
    for job in as_completed(jobs):
        try:
            print("gotowe", job.result())
        except Exception as e:
            print("problem", jobs[job], e)

Z wątkami nie szalej. Domyślnie klucz ma limit 60 zapytań na minutę, powyżej serwer zwraca 429. U mnie 4-8 równoległych zadań działa bez kłopotów.

Szablon promptu dla całej serii

W skryptach składam prompt z części: nazwa z bazy plus stały opis stylu. Styl trzymam w jednej stałej:

python
STYLE = ("product photo, soft light from the right, "
         "plain warm-grey background, shallow depth of field")

def product_prompt(name, material):
    return f"{name} made of {material}, centered, {STYLE}"

print(product_prompt("Desk lamp", "brushed brass"))

Zmiana światła w 200 obrazach to wtedy jedna linijka. Co jeszcze poprawiło moje wyniki:

  • Kolejność: obiekt, otoczenie, na końcu światło i kadr. Początek promptu waży najwięcej.
  • Konkret zamiast przymiotników: "brushed brass, visible fingerprints" mówi więcej niż "elegant lamp".
  • Napis do umieszczenia na obrazie - w cudzysłowie, w dokładnym brzmieniu.
  • Podobna kompozycja w serii: jeden seed dla wszystkich (tam, gdzie model go ma).
  • Prompt zapisuj obok pliku, choćby w JSON-ie. Za miesiąc nie będziesz pamiętać, co zadziałało.

Gdy coś pójdzie nie tak

Koderror.typeReakcja
401authentication_errorzły lub pusty klucz w zmiennej
402insufficient_balanceavailable niższe niż wycena - doładuj
404not_foundliterówka w identyfikatorze modelu
422invalid_requestpole spoza schematu, komunikat wskazuje które
429rate_limitodczekaj i spróbuj z opóźnieniem

Ponawianie przy 429 i 502 z rosnącą przerwą:

python
def post_with_retry(payload, tries=4):
    for n in range(tries):
        r = requests.post(f"{API}/generate",
                          params={"wait": 60},
                          headers=HEADERS,
                          json=payload, timeout=130)
        if r.status_code not in (429, 502):
            return r
        time.sleep(2 ** n * 3)
    return r

Wszystkie kody opisuje dokumentacja błędów, a endpoint - dokumentacja generowania.

Rachunek

Każdy model ma stałą stawkę za obraz: GPT Image 2.5 0,293 zł za obraz, Nano Banana Pro od 0,411 zł za obraz, FLUX.2 pro od 0,403 zł za obraz. Seria 30 grafik to po prostu 30 x stawka wybranego modelu. Wycenę każdego zadania masz w estimated_cost zaraz po wysłaniu, a zadania zakończone błędem nie są płatne.

Konto polskie rozlicza się w złotych brutto, fakturę VAT zaznaczasz przy doładowaniu. Bieżące stawki: cennik. Chcesz potem ożywić obraz? Podajesz go jako pierwszą klatkę wideo - przykład jest w poradniku Seedance 2.5 API.

Pytania

Po co ustawiać timeout większy niż wait?

Bo przy ?wait=60 serwer może trzymać połączenie przez całą minutę. Jeśli requests ma krótszy timeout, przerwie je, zanim przyjdzie odpowiedź, i stracisz wynik w skrypcie (zadanie i tak się wykona, odbierzesz je po id).

Jak dostać cztery warianty jednego promptu?

W modelach z num_images, np. Nano Banana 2, wpisujesz 4. W GPT Image 2.5, Nano Banana Pro i FLUX.2 pro wysyłasz cztery osobne zadania, najlepiej równolegle. Koszt w obu przypadkach to cztery obrazy.

Czy zdjęcie do edycji mogę wysłać jako plik z dysku?

W image_urls podajesz adresy HTTPS, więc plik musi być wcześniej wystawiony publicznie, np. w buckecie.

Co się dzieje z kosztem, gdy dostanę status failed?

Nic nie płacisz. Kwota zablokowana przy starcie wraca na saldo w całości.