Obraz z AI w Node.js na samym fetch - zlecenie, czekanie i zapis pliku
Generowanie obrazu AI w Node.js bez SDK: fetch do API Kadrarisa, sprawdzenie kodu HTTP, zapis id zadania, odpytywanie z limitem i zapis gotowego pliku.
Na jeden obraz z AI nie trzeba instalować żadnej paczki. Node.js 18+ ma wbudowany fetch i to wystarcza, żeby zlecić zadanie, poczekać na wynik i zapisać plik. Biblioteka jest tu najmniej ważna - liczą się trzy nawyki: sprawdzanie kodu HTTP przed czytaniem JSON-a, limit prób przy odpytywaniu i zapisanie identyfikatora zadania, zanim cokolwiek pójdzie źle.
Zanim uruchomisz skrypt
- Klucz w zmiennej środowiskowej
KADRARIS_API_KEY. Nie w kodzie, nie w repozytorium, nie w pliku z promptami. - Treść zlecenia to identyfikator modelu i obiekt
input. W tym przykładzie:
{
"model": "gpt-image-2.5",
"input": {
"prompt": "A copper kettle on a stone windowsill, soft daylight, product photo, no text"
}
}- Najpierw
res.ok, potem pola. Jeśli odpowiedź to 401, 402 albo 422, JSON nie maidi program wywróci się w dziwnym miejscu. idiestimated_costwypisuję na ekran i zapisuję do pliku. Po restarcie programu wracam do tego samego zadania.
Skrypt
Zapisz jako obraz.mjs i uruchom node obraz.mjs. Wynik trafia do wynik.bin jako surowe bajty - rozszerzenie nadaj według wypisanego Content-Type, a nie według tego, co było w prompcie.
import { writeFile } from "node:fs/promises";
import { setTimeout as sleep } from "node:timers/promises";
const base = "https://kadraris.com/v1";
const headers = {
Authorization: `Bearer ${process.env.KADRARIS_API_KEY}`,
"Content-Type": "application/json"
};
// jedno miejsce na timeout i sprawdzanie statusu HTTP
async function api(path, options = {}) {
const res = await fetch(base + path, {
...options, headers,
signal: AbortSignal.timeout(20000)
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
let task = await api("/generate", {
method: "POST",
body: JSON.stringify({
model: "gpt-image-2.5",
input: { prompt: "A copper kettle on a windowsill" }
})
});
// id na dysk: po restarcie wznawiasz tylko GET
await writeFile("task-id.txt", task.id);
console.log(task.id, task.estimated_cost);
// najwyżej 24 odczyty co 5 s, czyli około 2 minut
for (let i = 0; i < 24; i++) {
if (["succeeded", "failed"].includes(task.status)) break;
await sleep(5000);
task = await api(`/tasks/${encodeURIComponent(task.id)}`);
}
if (task.status === "failed") {
throw new Error(JSON.stringify(task.error));
}
if (task.status !== "succeeded") {
throw new Error(`Zadanie ${task.id} jeszcze trwa`);
}
const url = task.output?.[0]?.url;
if (!url) throw new Error("Zadanie bez pliku w output");
const file = await fetch(url, {
signal: AbortSignal.timeout(30000)
});
if (!file.ok) throw new Error(`Plik: HTTP ${file.status}`);
const bytes = new Uint8Array(await file.arrayBuffer());
await writeFile("wynik.bin", bytes);
console.log("wynik.bin,", file.headers.get("content-type"));Co skrypt robi w nietypowych sytuacjach
- Błąd sieci przy POST. Rzuca wyjątek i kończy. Nie ponawiaj zlecenia automatycznie - zadanie mogło powstać, choć odpowiedź nie dotarła. Najpierw zajrzyj do historii w panelu.
- Zadanie wciąż trwa po dwóch minutach. Program kończy się komunikatem, ale
task-id.txtzostaje. Uruchom później sam odczytGET /v1/tasks/{id}. failed. Wypisuje błąd i nie próbuje drugi raz. Wejście zmieniaj tylko wtedy, gdy rozumiesz przyczynę z komunikatu. Za takie zadanie nie płacisz.succeededbez pliku. Mało prawdopodobne, ale sprawdzone - lepiej jasny komunikat niżundefinedw adresie.
Na koniec trzy rzeczy do sprawdzenia przed wdrożeniem: klucz pochodzi ze środowiska, każdy nieudany kod HTTP jest obsłużony przed użyciem JSON-a, a pobrany plik ląduje w twoim magazynie.
Koszt
Model z przykładu, GPT Image 2.5, kosztuje 0,293 zł za obraz. Inne ustawienia przeliczysz w kalkulatorze, stawki wszystkich modeli są w cenniku. Saldo nie wygasa, abonamentu nie ma, a nieudane zadania są bezpłatne. Obraz, który się udał, jest płatny także wtedy, gdy go nie użyjesz.
Pytania
Dlaczego zapisuję plik od razu, a nie trzymam linku?
Adres w output działa ograniczony czas i nie jest archiwum. Zaakceptowany obraz przenieś do własnego magazynu, a w bazie trzymaj ścieżkę do swojej kopii.
Czy mogę ominąć pętlę odpytywania?
Tak - dodaj do adresu ?wait=60, a odpowiedź poczeka na wynik do 60 sekund (maksymalnie 120). Jeśli zadanie nie zdąży, dostaniesz zwykłe 202 i wracasz do pętli. Przy wielu zleceniach wygodniejszy jest webhook.
Gdzie sprawdzę pola modelu i cenę przed pierwszym uruchomieniem?
Pola opisują strona modelu i dokumentacja generowania. Cenę zobaczysz w Studio na przycisku przed generowaniem, a w API w estimated_cost. Wersję tego samego przepisu w Pythonie opisuję w generowaniu obrazów przez API.
























