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

Seedance 2.5 z poziomu kodu - curl, Python, webhook i obsługa błędów

Seedance 2.5 przez API: zlecenie wideo w curl i Pythonie, odbiór pliku MP4, webhook z podpisem HMAC, koszt za sekundę i kody błędów, które warto obsłużyć.

Do generowania wideo z Seedance 2.5 w skrypcie potrzebujesz dwóch wywołań. POST /v1/generate z modelem seedance-2.5 przyjmuje zlecenie i oddaje identyfikator zadania, a GET /v1/tasks/{id} po minucie lub kilku zwraca link do pliku MP4. Płacisz od 1,51 zł za sekundę za każdą sekundę gotowego filmu. W tym poradniku masz przykłady w curl i Pythonie, przeliczenie kosztów, odbiornik webhooka i tabelę błędów, z którymi sam się zetknąłem.

Co dostajesz w Seedance 2.5

Model pochodzi z ByteDance Seed i został pokazany 31 lipca 2026. Producent wymienia trzy główne cechy:

  • do 30 sekund filmu w jednym przebiegu,
  • dźwięk powstający razem z obrazem (efekty, mowa, muzyka),
  • szerokie wejście referencyjne: według ogłoszenia ByteDance jedno zadanie przyjmie do 30 obrazów, 10 filmów i 10 ścieżek audio.

Dochodzi do tego edycja gotowego wideo i przedłużanie klipu w kolejnych rundach.

Dla mnie liczy się przede wszystkim długość. Przy wersji 2.0 rolkę na 25 sekund trzeba było składać z kilku ujęć i pilnować, żeby postać między cięciami nie zmieniła twarzy. Z 2.5 cała rolka może być jednym ujęciem.

Rozdzielczości nie obiecuję na podstawie zapowiedzi, bo oficjalne materiały ByteDance jej nie podają, a różne serwisy oferują 480p, 720p, czasem 1080p. Dozwolone wartości w Kadrarisie odczytasz ze schematu modelu:

bash
curl https://kadraris.com/v1/models/seedance-2.5

Interesuje cię pole input_schema, w którym są dopuszczalne resolution, aspect_ratio i zakres duration. Ten adres jest publiczny i działa bez klucza.

Koszt za sekundę

Seedance 2.5 kosztuje od 1,51 zł za sekundę. Liczy się każda sekunda wygenerowanego filmu, więc cena klipu to stawka razy długość:

KlipWzórDo czego
5 s5 × od 1,51 zł za sekundęsprawdzenie promptu, przebitka
10 s10 × od 1,51 zł za sekundękrótkie ujęcie do reklamy
15 s15 × od 1,51 zł za sekundęrolka z jednego ujęcia
30 s30 × od 1,51 zł za sekundęcała rolka za jednym razem

Stawka w tabeli dotyczy 480p. Domyślne 720p kosztuje za sekundę ok. 2,2 raza więcej, a 1080p ok. 5,4 raza więcej, więc 10 s w 720p to ok. 34 zł.

Na koncie polskim kwoty są w złotówkach brutto (VAT 23%), a fakturę VAT zaznaczasz przy doładowaniu. Bieżące stawki modeli wideo są w cenniku, a różnice między odmianami Seedance opisałem w poradniku Seedance 2.0 czy 2.5.

Na końcowy rachunek mocniej niż stawka wpływają dwie sprawy:

  • Porażka nic nie kosztuje. Zadanie ze statusem failed zwalnia całą blokadę, a jego cost wynosi 0.
  • Nieudany pomysł już tak. Film, który się wygenerował, ale ci nie pasuje, jest udaną generacją. Dlatego próbuję prompty na 5 sekundach, a pełną długość puszczam na końcu.

Cenę znasz przed startem. Odpowiedź na POST /v1/generate zawiera estimated_cost, a tyle zostaje zablokowane na saldzie, dopóki zadanie się nie zakończy.

Pierwsze zlecenie z terminala

Najpierw konto i klucz. Rejestracja trwa chwilę, najmniejsze doładowanie to 40 zł, a klucz (sk-kd-...) generujesz w panelu. Krok po kroku opisuje to szybki start.

Klucz wkładam do zmiennej środowiskowej:

bash
export KADRARIS_API_KEY="sk-kd-..."

Zlecenie pięciosekundowego klipu:

bash
curl https://kadraris.com/v1/generate \
  -H "Authorization: Bearer $KADRARIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "input": {
      "prompt": "A fisherman pulls a net onto a wooden boat at dawn, slow handheld camera, mist over the water, waves and seagulls",
      "duration": 5,
      "resolution": "720p",
      "aspect_ratio": "16:9",
      "generate_audio": true
    }
  }'

Odpowiedź przychodzi natychmiast, z kodem 202:

json
{
  "id": "task_8f2k...",
  "object": "task",
  "status": "queued",
  "model": "seedance-2.5",
  "estimated_cost": { "amount": "...", "currency": "PLN" },
  "poll_url": "https://kadraris.com/v1/tasks/task_8f2k..."
}

A tak sprawdzasz, co się dzieje z zadaniem:

bash
curl https://kadraris.com/v1/tasks/task_8f2k... \
  -H "Authorization: Bearer $KADRARIS_API_KEY"

Kiedy status przejdzie w succeeded, w output pojawi się link do pliku. Działa ograniczony czas, więc ściągaj od razu.

Skrypt w Pythonie od zlecenia do pliku

Ten kod zleca film, czeka na wynik z coraz dłuższymi przerwami między zapytaniami i zapisuje MP4. Na nim opieram większość własnych prób.

python
import os
import time
import pathlib
import requests

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


def generate_video(prompt, duration=5, aspect_ratio="16:9",
                   resolution="720p", image_url=None):
    payload = {
        "model": "seedance-2.5",
        "input": {
            "prompt": prompt,
            "duration": duration,
            "resolution": resolution,
            "aspect_ratio": aspect_ratio,
            "generate_audio": True,
        },
    }
    if image_url:
        payload["input"]["image_url"] = image_url

    r = requests.post(f"{API}/generate", headers=HEADERS,
                      json=payload, timeout=30)
    if r.status_code != 202:
        msg = r.json()["error"]["message"]
        raise RuntimeError(f"{r.status_code}: {msg}")
    task = r.json()
    print("id:", task["id"], "szacunek:", task["estimated_cost"])
    return task


def wait_for_task(task_id, timeout=900):
    # przerwy rosną od 3 do 20 sekund
    delay, deadline = 3, 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(delay)
        delay = min(delay * 1.5, 20)
    raise TimeoutError(task_id)


def download(task, folder="wideo"):
    pathlib.Path(folder).mkdir(exist_ok=True)
    saved = []
    for i, item in enumerate(task["output"]):
        path = pathlib.Path(folder) / f"{task['id']}_{i}.mp4"
        with requests.get(item["url"], stream=True,
                          timeout=180) as r:
            r.raise_for_status()
            with open(path, "wb") as f:
                for chunk in r.iter_content(1 << 20):
                    f.write(chunk)
        saved.append(path)
    return saved


if __name__ == "__main__":
    task = generate_video(
        "Close-up of hands kneading dough on a floured "
        "table, warm kitchen light, slow push-in, "
        "soft ambient sound",
        duration=5,
    )
    done = wait_for_task(task["id"])
    if done["status"] == "succeeded":
        print("koszt:", done["cost"], "pliki:", download(done))
    else:
        print("porażka, 0 zł:", done["error"])

Co warto wiedzieć o tym kodzie:

  • Przy POST wystarczy timeout=30. Serwer tylko przyjmuje zlecenie i nie czeka na film.
  • Odpytywanie zaczyna się co 3 sekundy i zwalnia do 20. Częstsze sprawdzanie przy wideo nic nie daje.
  • generate_audio: True włącza dźwięk generowany razem z obrazem. Jeśli podkład dodajesz w montażu, daj False.
  • ?wait=60 działa też przy wideo, ale Seedance zwykle potrzebuje ponad minuty, więc tu z niego nie korzystam. Przy grafikach to wygodny skrót, o którym piszę w poradniku o generowaniu obrazów w Pythonie.

Pierwsza klatka z obrazu

Gdy zależy mi na konkretnym kadrze, np. produkcie w danym ustawieniu albo postaci z wcześniejszej grafiki, przekazuję obraz w image_url. Plik musi być dostępny pod publicznym adresem HTTPS.

python
task = generate_video(
    prompt="The camera slowly orbits the bottle, light "
           "reflections move across the glass, "
           "condensation drops run down",
    duration=5,
    aspect_ratio="9:16",
    image_url="https://twoja-domena.pl/pliki/butelka.png",
)

Klatkę startową robię zwykle w Nano Banana Pro albo GPT Image 2.5 i dopiero ją animuję. Tak wychodzi taniej: obraz kosztuje mniej niż sekunda filmu, więc kompozycję poprawiam na grafikach, a do Seedance idzie gotowy kadr. Koszty całego łańcucha rozpisałem w tekście ile kosztuje rolka z AI.

Powiadomienie zamiast pętli

Przy kilkunastu równoległych zadaniach odpytywanie robi się kłopotliwe. Dodaję wtedy webhook_url. Po zakończeniu zadania Kadraris wysyła na ten adres POST z tym samym JSON-em, który zwraca GET /v1/tasks/{id}.

bash
curl https://kadraris.com/v1/generate \
  -H "Authorization: Bearer $KADRARIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5",
    "input": { "prompt": "Rain on a neon street at night, slow dolly", "duration": 10, "aspect_ratio": "9:16" },
    "webhook_url": "https://twoj-serwer.pl/kadraris-hook"
  }'

Każde powiadomienie niesie nagłówek x-kadraris-signature, czyli podpis HMAC-SHA256 treści żądania. Sekret znajdziesz w panelu. Najprostszy odbiornik we Flasku:

python
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["KADRARIS_WEBHOOK_SECRET"].encode()

@app.post("/kadraris-hook")
def hook():
    body = request.get_data()
    expected = hmac.new(SECRET, body,
                        hashlib.sha256).hexdigest()
    got = request.headers.get("x-kadraris-signature", "")
    # odrzuć wszystko bez poprawnego podpisu
    if not hmac.compare_digest(expected, got):
        abort(401)
    task = request.get_json()
    if task["status"] == "succeeded":
        print("plik:", task["output"][0]["url"])
    return "", 204

Format podpisu i zasady ponawiania opisuje dokumentacja webhooków.

Jak piszę prompty pod Seedance 2.5

Po kilkudziesięciu generacjach trzymam się kilku reguł:

  1. Angielski. Polski opis model zrozumie, ale po angielsku ruch kamery wychodzi mi bardziej przewidywalnie.
  2. Kamera nazwana wprost: "slow push-in", "handheld", "static tripod shot", "orbit". Bez tego model sam wymyśla ruch i zwykle przesadza.
  3. Dźwięk w opisie. Przy generate_audio: true dopisuję na koniec, co słychać, np. "waves and seagulls" albo "quiet cafe chatter".
  4. Dialog w cudzysłowie, po angielsku. Polską mowę w modelach wideo traktuję jak loterię, a lektora nagrywam osobno.
  5. Jedna akcja na ujęcie. Pół minuty kusi, żeby zmieścić całą historię. Lepiej sprawdza się jedna czynność opisana dokładnie.

Więcej o budowie opisu ujęcia znajdziesz w poradniku prompty do wideo AI.

Kody błędów

KodPrzyczynaMoja reakcja
401brak klucza albo zły kluczsprawdzam KADRARIS_API_KEY
402saldo mniejsze niż estimated_costdoładowuję, BLIK wpływa od razu
404literówka w nazwie modelupoprawiam na seedance-2.5
422parametr spoza schematu, np. rozdzielczośćczytam error.message, porównuję z input_schema
429za dużo zapytańodczekuję i ponawiam z opóźnieniem
502błąd po stronie dostawcy modelupróbuję później

Zadanie, które zostało przyjęte i dopiero potem padło, nie daje błędu HTTP. Ma status: "failed" i obiekt error. Komplet kodów jest w dokumentacji błędów.

Kiedy inny model

Seedance 2.5 biorę, gdy potrzebuję długiego ujęcia z dźwiękiem albo wielu referencji. Do krótkich, filmowych ujęć z dopracowanym światłem sprawdzam też Veo 3.1 (Quality), a przy scenach z kilkoma ujęciami Kling 3.0. Tabelę parametrów i cen znajdziesz w porównaniu API do wideo AI. Jeśli wolisz klikać, ten sam model i to samo saldo masz w Kadraris Studio.

Pytania

Czy mogę użyć Seedance 2.5 Eco w tym samym skrypcie?

Tak. Pola są takie same, więc zmieniasz tylko "model": "seedance-2.5" na "seedance-2.5-eco". Eco ma węższą listę rozdzielczości i niższą stawkę (od 2,54 zł za sekundę), więc sprawdź dozwolone wartości w jego schemacie.

Jak ustalić maksymalną długość klipu dostępną przez API?

Odczytaj input_schema z GET /v1/models/seedance-2.5. Pole duration ma tam minimum i maksimum. Producent podaje do 30 sekund w jednym przebiegu.

Co się stanie, jeśli skrypt przerwie się w trakcie czekania?

Zadanie dalej się renderuje. Zachowaj id i wywołaj samą funkcję wait_for_task po restarcie, zamiast zlecać film od nowa. Podwójne zlecenie to dwie płatne generacje.

Czy webhook przyjdzie także po nieudanym zadaniu?

Tak, powiadomienie dotyczy zakończenia zadania, więc dostaniesz je ze statusem failed i obiektem error. Taka generacja kosztuje 0 zł.