Generowanie obrazów AI przez API w Pythonie - poradnik
Generowanie obrazów przez API w Pythonie krok po kroku: GPT Image 2.5, Nano Banana Pro i FLUX.2 pro, edycja zdjęć, seria obrazów i obsługa błędów.
Żeby wygenerować obraz AI w Pythonie, wystarczy jedno zapytanie POST z biblioteką requests: podajesz model (np. gpt-image-2.5, nano-banana-pro albo flux-2-pro), prompt i proporcje, a z parametrem ?wait=60 dostajesz gotowy link do pliku w tej samej odpowiedzi. Poniżej pokazuję cały proces na API Jantarisa: od klucza, przez pierwszy obraz, edycję zdjęcia i serię obrazów, po obsługę błędów. Każdy fragment kodu da się skopiować i uruchomić.
Czego potrzebujesz
- Python 3.10 lub nowszy i biblioteka
requests(pip install requests). - Konto w Jantarisie i klucz API. Zakładasz je na stronie rejestracji, doładowujesz saldo od 40 zł (BLIK albo karta) i tworzysz klucz
sk-jt-...w panelu. - Do edycji zdjęć: miejsce, gdzie wystawisz pliki pod publicznym adresem HTTPS (bucket, serwer, CDN).
- Klucz w zmiennej środowiskowej, nie w kodzie:
export JANTARIS_API_KEY="sk-jt-..."W PowerShellu: $env:JANTARIS_API_KEY = "sk-jt-...".
Który model do obrazów wybrać
Trzy modele, których używam najczęściej, różnią się bardziej charakterem niż jakością:
| Model | Cena za obraz | Mocna strona | Kiedy go wybieram |
|---|---|---|---|
| GPT Image 2.5 | 0,254 zł za obraz | precyzyjna edycja, trzymanie się poleceń w kolejnych poprawkach | grafiki z instrukcjami krok po kroku, edycje zdjęć |
| Nano Banana Pro | od 0,439 zł za obraz | tekst na obrazie, wiele referencji, spójność postaci | plakaty, infografiki, ta sama osoba w kilku scenach |
| FLUX.2 [pro] | od 0,404 zł za obraz | fotorealizm, faktury materiałów | zdjęcia produktowe, wnętrza, portrety |
Kilka faktów od producentów. GPT Image 2.5 (OpenAI, wrzesień 2026) przyjmuje do 16 obrazów referencyjnych i generuje do 4K. Nano Banana Pro, czyli Gemini 3 Pro Image od Google, łączy do 14 obrazów wejściowych i według Google trzyma spójny wygląd do 5 osób. FLUX.2 pro od Black Forest Labs generuje i edytuje do 4 megapikseli i przyjmuje kilka obrazów referencyjnych naraz. Limity obowiązujące w Jantarisie dla każdego modelu sprawdzisz w schemacie parametrów:
curl https://jantaris.com/v1/models/nano-banana-proPole input_schema mówi, które parametry model przyjmuje i jakie mają zakresy. Ten endpoint nie wymaga klucza.
Pierwszy obraz w Pythonie
Obrazy zwykle generują się w kilkanaście, kilkadziesiąt sekund, więc używam parametru ?wait=60. Serwer czeka na wynik i odpowiada gotowym zadaniem. Jeśli nie zdąży, dostajesz kod 202 i odpytujesz zadanie po id.
import os
import requests
API = "https://jantaris.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['JANTARIS_API_KEY']}"}
r = requests.post(
f"{API}/generate",
params={"wait": 60},
headers=HEADERS,
json={
"model": "gpt-image-2.5",
"input": {
"prompt": "Ceramic coffee mug on a concrete kitchen counter, soft morning light, "
"shallow depth of field, product photo",
"aspect_ratio": "1536x1024", # GPT Image 2.5: rozmiar w pikselach albo "auto"
},
},
timeout=130, # dłużej niż wait
)
task = r.json()
print(r.status_code, task["status"])
if task["status"] == "succeeded":
print(task["output"][0]["url"], task["cost"])Dwie rzeczy łatwo przeoczyć. Timeout klienta musi być dłuższy niż wait, inaczej requests zrezygnuje, zanim serwer odpowie. I zawsze sprawdzaj pole status, bo zakończone zadanie może mieć też failed. Wtedy nic nie płacisz, blokada środków wraca na saldo.
Funkcja pomocnicza: generowanie, czekanie i zapis
W prawdziwym skrypcie opakowuję to w jedną funkcję, która obsługuje też przypadek, gdy 60 sekund nie wystarczy:
import time
import pathlib
def wait_for_task(task_id: str, timeout: int = 300) -> dict:
deadline = time.time() + timeout
while time.time() < deadline:
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: str, prompt: str, folder: str = "obrazy", **params) -> list[pathlib.Path]:
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"generacja nieudana (0 zł): {task['error']['message']}")
pathlib.Path(folder).mkdir(exist_ok=True)
paths = []
for i, item in enumerate(task["output"]):
path = pathlib.Path(folder) / f"{model}_{task['id']}_{i}.png"
path.write_bytes(requests.get(item["url"], timeout=60).content)
paths.append(path)
return paths
print(generate_image("nano-banana-2", "Scandinavian living room, linen sofa, oak floor, overcast daylight",
aspect_ratio="16:9", num_images=2))Linki w output są ważne ograniczony czas, dlatego funkcja od razu zapisuje pliki na dysku. Rozszerzenie .png to uproszczenie, jeśli potrzebujesz dokładnego formatu, sprawdź nagłówek Content-Type przy pobieraniu.
Parametry, które naprawdę coś zmieniają
Parametry wspólne dla modeli obrazów w API Jantarisa:
| Parametr | Wartości | Do czego |
|---|---|---|
prompt | tekst | opis obrazu albo polecenie edycji |
image_urls | lista adresów HTTPS | edycja albo referencje (limit liczby zdjęć zależy od modelu) |
Reszta zależy od modelu. aspect_ratio w FLUX.2 pro i Nano Banana Pro to proporcje typu 4:3, a w GPT Image 2.5 rozmiar w pikselach (1024x1024, 1536x1024, 1024x1536) albo auto. num_images, seed, negative_prompt, resolution i quality mają tylko niektóre modele. Dokładną listę pól i dozwolonych wartości zwraca GET /v1/models/{id} w polu input_schema. Pole albo wartość spoza schematu kończy się błędem 422, zanim cokolwiek zapłacisz.
num_images (1-4) przyjmują m.in. Nano Banana 2 i Wan 2.7 Image. GPT Image 2.5, Nano Banana Pro i FLUX.2 pro zwracają jeden obraz na zadanie, więc kilka wariantów to kilka zadań. num_images mnoży koszt: cztery warianty to czterokrotność ceny za obraz. Przy szukaniu kompozycji biorę 2-4 warianty, wybieram najlepszy i dalej poprawiam już tylko jego, edycją zamiast losowania od nowa.
seed przydaje się, gdy chcę zmienić jedno słowo w prompcie i zobaczyć, co się zmienia. Mają go m.in. FLUX.2 [max], FLUX.1 Kontext [pro] i Wan 2.7 Image, a trzy modele z tego poradnika go nie przyjmują. Ten sam seed i ten sam prompt dają podobny wynik, choć nie zawsze identyczny co do piksela.
Edycja zdjęcia przez API
Edycja to ten sam endpoint, tylko z image_urls. Zdjęcie musi być dostępne pod publicznym adresem HTTPS, np. w twoim buckecie albo na serwerze.
paths = generate_image(
"gpt-image-2.5",
"Replace the background with a bright white studio backdrop. "
"Keep the product, its shadow and its label exactly as they are.",
image_urls=["https://twoja-domena.pl/pliki/buty-surowe.jpg"],
)Przy edycji piszę w prompcie wprost, co ma zostać bez zmian. Bez tego modele lubią "poprawić" przy okazji rzeczy, o które nie prosiłem, np. kolor produktu. GPT Image 2.5 jest w tym najbardziej posłuszny, dlatego do edycji zdjęć produktowych wybieram go najczęściej.
Kilka referencji naraz, np. ta sama postać w nowej scenie:
paths = generate_image(
"nano-banana-pro",
"The woman from the first image sits at the cafe table from the second image, "
"reading a book, same outfit, evening light",
image_urls=[
"https://twoja-domena.pl/pliki/postac.jpg",
"https://twoja-domena.pl/pliki/kawiarnia.jpg",
],
aspect_ratio="9:16",
)Więcej przykładów edycji z polskimi promptami jest w tekście Nano Banana Pro po polsku.
Seria obrazów równolegle
Kiedy potrzebuję 30 grafik do artykułów albo 20 wariantów banera, nie czekam na każdą osobno. Wysyłam kilka zadań równolegle przez ThreadPoolExecutor:
from concurrent.futures import ThreadPoolExecutor, as_completed
PROMPTY = [
"Flat illustration of a person budgeting on a laptop, pastel colors",
"Flat illustration of a family planting a tree, pastel colors",
"Flat illustration of a cyclist in the city, pastel colors",
"Flat illustration of a woman reading in a hammock, pastel colors",
]
with ThreadPoolExecutor(max_workers=4) as pool:
futures = {pool.submit(generate_image, "flux-2-pro", p, aspect_ratio="16:9"): p for p in PROMPTY}
for f in as_completed(futures):
try:
print("OK", f.result())
except Exception as e:
print("BŁĄD", futures[f], e)Nie przesadzaj z liczbą wątków. Domyślny limit to 60 zapytań na minutę na klucz, po przekroczeniu dostajesz 429. Cztery do ośmiu równoległych zadań to u mnie rozsądny zakres.
Jak pisać prompty do obrazów w API
W skryptach prompt często składam z kawałków, np. z nazwy produktu z bazy i stałego opisu stylu. Taki szablon trzymam w jednym miejscu:
STYL = "product photo, soft daylight from the left, neutral background, shallow depth of field"
def prompt_produktu(nazwa: str, material: str) -> str:
return f"{nazwa} made of {material}, centered, {STYL}"
print(prompt_produktu("Coffee mug", "matte sand-colored ceramic"))Dzięki temu cała seria ma ten sam styl, a zmiana oświetlenia w 200 obrazach to edycja jednej linijki. Kilka zasad, które u mnie poprawiły wyniki:
- Najpierw obiekt, potem otoczenie, na końcu światło i kadr. Modele zwracają większą uwagę na początek promptu.
- Konkretne słowa zamiast ocen: "matte ceramic, visible glaze drips" zamiast "beautiful mug".
- Tekst, który ma się pojawić na obrazie, w cudzysłowie i w dokładnym brzmieniu.
- Przy serii ten sam
seeddla wszystkich obrazów, jeśli chcesz podobnej kompozycji (w modelach, które mają to pole). - Zapisuj prompt razem z plikiem, np. w nazwie albo w pliku JSON obok. Po miesiącu nie będziesz pamiętać, co dało ten dobry wynik.
Obsługa błędów
| Kod | error.type | Co zrobić |
|---|---|---|
| 401 | authentication_error | sprawdź klucz w zmiennej środowiskowej |
| 402 | insufficient_balance | doładuj saldo, available jest niższe niż szacowany koszt |
| 404 | not_found | literówka w nazwie modelu |
| 422 | invalid_request | parametr poza schematem, komunikat mówi, które pole |
| 429 | rate_limit | odczekaj i ponów z opóźnieniem |
Prosty wrapper z ponawianiem przy 429 i 502:
def post_with_retry(payload: dict, tries: int = 4) -> requests.Response:
for attempt 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 ** attempt * 3)
return rPełna lista kodów jest w dokumentacji błędów, a opis endpointu w dokumentacji generowania.
Ile to kosztuje
Każdy model ma cenę za obraz: 0,254 zł za obraz dla GPT Image 2.5, od 0,439 zł za obraz dla Nano Banana Pro i od 0,404 zł za obraz dla FLUX.2 pro. Seria 30 obrazów to trzydziestokrotność ceny jednego, a kwotę dla każdego zadania widzisz w polu estimated_cost od razu po jego wysłaniu. Nieudane generacje nie są płatne.
Na koncie PL ceny są w złotówkach brutto, a fakturę VAT dostajesz po każdym doładowaniu. Aktualne stawki: cennik. Jeśli z obrazu chcesz potem zrobić wideo, obraz podajesz jako pierwszą klatkę, co opisałem w poradniku Seedance 2.5 API.
FAQ
Jak wygenerować obraz AI w Pythonie?
Wyślij POST https://jantaris.com/v1/generate?wait=60 z nagłówkiem Authorization: Bearer sk-jt-... i ciałem {"model": "gpt-image-2.5", "input": {"prompt": "..."}}. W odpowiedzi, w polu output, dostaniesz link do gotowego obrazu.
Który model jest najlepszy do tekstu na obrazie?
Do napisów na plakatach i infografikach najczęściej wybieram Nano Banana Pro. Przy polskich znakach zawsze sprawdzam ogonki, bo błędy w diakrytykach wciąż się zdarzają.
Czy mogę edytować własne zdjęcia przez API?
Tak. Podaj adres zdjęcia w image_urls i opisz w prompcie zmianę oraz to, co ma zostać bez zmian. Plik musi być dostępny pod publicznym adresem HTTPS.
Ile obrazów mogę wygenerować w jednym zapytaniu?
W modelach z parametrem num_images, np. Nano Banana 2, od 1 do 4. GPT Image 2.5, Nano Banana Pro i FLUX.2 pro zwracają jeden obraz na zadanie. Każdy obraz jest liczony osobno, więc koszt rośnie proporcjonalnie.
Czy płacę za obraz, który się nie wygenerował?
Nie. Zadanie ze statusem failed kosztuje 0, a zablokowana kwota wraca na saldo.












