This commit is contained in:
Steve Tietze
2025-12-19 11:07:10 +07:00
parent ed2f06c0b8
commit a3aba13fba
9 changed files with 242 additions and 122 deletions
+171
View File
@@ -0,0 +1,171 @@
POST
/api/v1/jobs/createTask
Create Task
Create a new generation task
Request Parameters
The API accepts a JSON payload with the following structure:
Request Body Structure
{
"model": "string",
"callBackUrl": "string (optional)",
"input": {
// Input parameters based on form configuration
}
}
Root Level Parameters
model
Required
string
The model name to use for generation
Example:
"bytedance/v1-pro-fast-image-to-video"
callBackUrl
Optional
string
Callback URL for task completion notifications. Optional parameter. If provided, the system will send POST requests to this URL when the task completes (success or failure). If not provided, no callback notifications will be sent.
Example:
"https://your-domain.com/api/callback"
Input Object Parameters
The input object contains the following parameters based on the form configuration:
input.prompt
Required
string
The text prompt used to generate the video
Max length: 10000 characters
Example:
"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw"
input.image_url
Required
string(URL)
The URL of the image used to generate video
File URL after upload, not file content; Accepted types: image/jpeg, image/png, image/webp; Max size: 10.0MB
Example:
"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp"
input.resolution
Optional
string
Video resolution - 480p for faster generation, 720p for balance, 1080p for higher quality
Available options:
720p
-
720p
1080p
-
1080p
Example:
"720p"
input.duration
Optional
string
Duration of the video in seconds
Available options:
5
-
5s
10
-
10s
Example:
"5"
Request Example
cURL
JavaScript
Python
import requests
import json
url = "https://api.kie.ai/api/v1/jobs/createTask"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"
}
payload = {
"model": "bytedance/v1-pro-fast-image-to-video",
"callBackUrl": "https://your-domain.com/api/callback",
"input": {
"prompt": "A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw",
"image_url": "https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp",
"resolution": "720p",
"duration": "5"
}
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
result = response.json()
print(result)
Response Example
{
"code": 200,
"message": "success",
"data": {
"taskId": "task_12345678"
}
}
Response Fields
code
Status code, 200 for success, others for failure
message
Response message, error description when failed
data.taskId
Task ID for querying task status
Callback Notifications
When you provide the callBackUrl parameter when creating a task, the system will send POST requests to the specified URL upon task completion (success or failure).
Success Callback Example
{
"code": 200,
"data": {
"completeTime": 1755599644000,
"consumeCredits": 100,
"costTime": 8,
"createTime": 1755599634000,
"model": "bytedance/v1-pro-fast-image-to-video",
"param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"bytedance/v1-pro-fast-image-to-video\",\"input\":{\"prompt\":\"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw\",\"image_url\":\"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp\",\"resolution\":\"720p\",\"duration\":\"5\"}}",
"remainedCredits": 2510330,
"resultJson": "{\"resultUrls\":[\"https://example.com/generated-image.jpg\"]}",
"state": "success",
"taskId": "e989621f54392584b05867f87b160672",
"updateTime": 1755599644000
},
"msg": "Playground task completed successfully."
}
Failure Callback Example
{
"code": 501,
"data": {
"completeTime": 1755597081000,
"consumeCredits": 0,
"costTime": 0,
"createTime": 1755596341000,
"failCode": "500",
"failMsg": "Internal server error",
"model": "bytedance/v1-pro-fast-image-to-video",
"param": "{\"callBackUrl\":\"https://your-domain.com/api/callback\",\"model\":\"bytedance/v1-pro-fast-image-to-video\",\"input\":{\"prompt\":\"A cinematic close-up sequence of a single elegant ceramic coffee cup with saucer on a rustic wooden table near a sunlit window, hot rich espresso poured in a thin golden stream from above, gradually filling the cup in distinct stages: empty with faint steam, 1/4 filled with dark crema, half-filled with swirling coffee and rising steam, 3/4 filled nearing the rim, perfectly full just below overflow with glossy surface and soft bokeh highlights; ultra-realistic, warm golden-hour light, shallow depth of field, photorealism, detailed textures, subtle steam wisps, serene inviting atmosphere --ar 16:9 --q 2 --style raw\",\"image_url\":\"https://file.aiquickdraw.com/custom-page/akr/section-images/1762340693669m6sey187.webp\",\"resolution\":\"720p\",\"duration\":\"5\"}}",
"remainedCredits": 2510430,
"state": "fail",
"taskId": "bd3a37c523149e4adf45a3ddb5faf1a8",
"updateTime": 1755597097000
},
"msg": "Playground task failed."
}
+16 -1
View File
@@ -4,6 +4,8 @@ import torch
import numpy as np
from PIL import Image
from .http import requests
def _image_bytes_to_tensor(image_bytes: bytes) -> torch.Tensor:
"""Convert image bytes into a normalized torch tensor.
@@ -24,4 +26,17 @@ def _image_bytes_to_tensor(image_bytes: bytes) -> torch.Tensor:
tensor = tensor.float() / 255.0
return tensor.unsqueeze(0)
except Exception as exc:
raise RuntimeError("Failed to decode result image.") from exc
raise RuntimeError("Failed to decode result image.") from exc
def _download_image(url: str) -> bytes:
"""Download a result image and return its raw bytes."""
try:
response = requests.get(url, timeout=120)
except requests.RequestException as exc:
raise RuntimeError(f"Failed to download result image: {exc}") from exc
if response.status_code != 200:
raise RuntimeError(f"Failed to download result image (status code {response.status_code}).")
return response.content
+1 -6
View File
@@ -15,16 +15,12 @@ import time
from typing import Any
from .http import TransientKieError, requests
from .log import _log
RECORD_INFO_URL = "https://api.kie.ai/api/v1/jobs/recordInfo"
def _log(enabled: bool, msg: str) -> None:
if enabled:
print(f"[KIE] {msg}")
def _fetch_task_record(api_key: str, task_id: str) -> tuple[dict[str, Any], str, Any]:
"""Fetch the current record for a task.
@@ -163,4 +159,3 @@ def _poll_task_until_complete(
if should_log:
_log(log, f"Polling again in {interval} seconds...")
time.sleep(interval)
+7
View File
@@ -0,0 +1,7 @@
"""Shared logging helpers for KIE API modules."""
def _log(enabled: bool, msg: str) -> None:
"""Log a message to stdout when verbose logging is enabled."""
if enabled:
print(f"[KIE] {msg}")
+6 -90
View File
@@ -12,20 +12,19 @@ This file is intentionally model-specific and not generic.
import json
import time
from io import BytesIO
from typing import Any
import torch
from PIL import Image
from .auth import _load_api_key
from .credits import _log_remaining_credits
from .http import TransientKieError, requests
from .jobs import _fetch_task_record, _poll_task_until_complete, _should_retry_fail
from .log import _log
from .results import _extract_result_urls
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image
from .images import _image_bytes_to_tensor
from .images import _download_image, _image_bytes_to_tensor
from .validation import _validate_prompt
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
RECORD_INFO_URL = "https://api.kie.ai/api/v1/jobs/recordInfo"
@@ -33,52 +32,7 @@ MODEL_NAME = "nano-banana-pro"
ASPECT_RATIO_OPTIONS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "auto"]
RESOLUTION_OPTIONS = ["1K", "2K", "4K"]
OUTPUT_FORMAT_OPTIONS = ["png", "jpg"]
# -----------------------------
# Constants / enums
# -----------------------------
# -----------------------------
# Validation
# -----------------------------
# -----------------------------
# Request building (Nano Banana specific)
# -----------------------------
# -----------------------------
# Task lifecycle (submit + poll)
# -----------------------------
# -----------------------------
# Result handling (parse + download + decode)
# -----------------------------
def _log(enabled: bool, msg: str) -> None:
"""Log a message to stdout when verbose logging is enabled.
Returns:
None.
Raises:
None.
"""
if enabled:
print(f"[KIE] {msg}")
def _validate_prompt(prompt: str) -> None:
"""Validate presence and length of the generation prompt.
Returns:
None.
Raises:
RuntimeError: If the prompt is empty or exceeds 10000 characters.
"""
if not prompt:
raise RuntimeError("Prompt is required.")
if len(prompt) > 10000:
raise RuntimeError("Prompt exceeds the maximum length of 10000 characters.")
PROMPT_MAX_LENGTH = 10000
def _create_nano_banana_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]:
@@ -121,8 +75,6 @@ def _create_nano_banana_task(api_key: str, payload: dict[str, Any]) -> tuple[str
return task_id, response.text
def _create_nanobanana_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]:
"""Backward-compatible alias for node usage."""
return _create_nano_banana_task(api_key, payload)
@@ -147,47 +99,11 @@ def _poll_nanobanana_until_complete(
return _poll_task_until_complete(api_key, task_id, poll_interval_s, timeout_s, log, start_time)
def _extract_result_urls(record_data: dict[str, Any]) -> list[str]:
result_json = record_data.get("resultJson")
if not result_json:
raise RuntimeError("Task completed without resultJson.")
try:
parsed = json.loads(result_json)
except json.JSONDecodeError as exc:
raise RuntimeError("resultJson is not valid JSON.") from exc
result_urls = parsed.get("resultUrls")
if not result_urls or not isinstance(result_urls, list):
raise RuntimeError("resultJson does not contain resultUrls.")
return result_urls
def _extract_nanobanana_result_urls(record_data: dict[str, Any]) -> list[str]:
"""Backward-compatible alias for node usage."""
return _extract_result_urls(record_data)
def _download_image(url: str) -> bytes:
"""Download a result image and return its raw bytes.
Returns:
The downloaded image content.
Raises:
RuntimeError: If the download fails or returns a non-200 status.
"""
try:
response = requests.get(url, timeout=120)
except requests.RequestException as exc:
raise RuntimeError(f"Failed to download result image: {exc}") from exc
if response.status_code != 200:
raise RuntimeError(f"Failed to download result image (status code {response.status_code}).")
return response.content
def _download_nanobanana_image(url: str) -> bytes:
"""Backward-compatible alias for node usage."""
return _download_image(url)
@@ -217,7 +133,7 @@ def run_nanobanana_image_job(
RuntimeError: For validation errors, non-retryable API failures, timeouts, or decoding failures.
TransientKieError: For retryable API/task failures (used to trigger retries when enabled).
"""
_validate_prompt(prompt)
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
+22
View File
@@ -0,0 +1,22 @@
"""Shared helpers for parsing KIE job results."""
import json
from typing import Any
def _extract_result_urls(record_data: dict[str, Any]) -> list[str]:
"""Extract the resultUrls list from a recordInfo payload."""
result_json = record_data.get("resultJson")
if not result_json:
raise RuntimeError("Task completed without resultJson.")
try:
parsed = json.loads(result_json)
except json.JSONDecodeError as exc:
raise RuntimeError("resultJson is not valid JSON.") from exc
result_urls = parsed.get("resultUrls")
if not result_urls or not isinstance(result_urls, list):
raise RuntimeError("resultJson does not contain resultUrls.")
return result_urls
+5 -11
View File
@@ -20,10 +20,12 @@ import torch
from .auth import _load_api_key
from .credits import _log_remaining_credits
from .http import TransientKieError, requests
from .images import _image_bytes_to_tensor
from .images import _download_image, _image_bytes_to_tensor
from .jobs import _poll_task_until_complete
from .nanobanana import _download_image, _extract_result_urls, _log
from .log import _log
from .results import _extract_result_urls
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image
from .validation import _validate_prompt
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
@@ -33,13 +35,6 @@ QUALITY_OPTIONS = ["basic", "high"]
PROMPT_MAX_LENGTH = 3000
def _validate_prompt(prompt: str) -> None:
if not prompt:
raise RuntimeError("Prompt is required.")
if len(prompt) > PROMPT_MAX_LENGTH:
raise RuntimeError("Prompt exceeds the maximum length of 3000 characters.")
def _validate_options(aspect_ratio: str, quality: str) -> None:
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
@@ -118,7 +113,7 @@ def run_seedream45_edit(
RuntimeError: For validation errors or non-retryable API/task failures.
TransientKieError: For retryable API/task failures.
"""
_validate_prompt(prompt)
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
_validate_options(aspect_ratio, quality)
images = _validate_image_input(images)
@@ -167,4 +162,3 @@ def run_seedream45_edit(
_log_remaining_credits(log, record_data, api_key, _log)
return image_tensor
+5 -14
View File
@@ -9,13 +9,11 @@ import torch
from .auth import _load_api_key
from .credits import _log_remaining_credits
from .http import TransientKieError, requests
from .images import _image_bytes_to_tensor
from .images import _download_image, _image_bytes_to_tensor
from .jobs import _poll_task_until_complete
from .nanobanana import (
_download_image,
_extract_result_urls,
_log,
)
from .log import _log
from .results import _extract_result_urls
from .validation import _validate_prompt
CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask"
@@ -25,13 +23,6 @@ QUALITY_OPTIONS = ["basic", "high"]
PROMPT_MAX_LENGTH = 3000
def _validate_prompt(prompt: str) -> None:
if not prompt:
raise RuntimeError("Prompt is required.")
if len(prompt) > PROMPT_MAX_LENGTH:
raise RuntimeError("Prompt exceeds the maximum length of 3000 characters.")
def _validate_options(aspect_ratio: str, quality: str) -> None:
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
@@ -79,7 +70,7 @@ def run_seedream45_text_to_image(
timeout_s: int,
log: bool,
) -> torch.Tensor:
_validate_prompt(prompt)
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
_validate_options(aspect_ratio, quality)
api_key = _load_api_key()
+9
View File
@@ -0,0 +1,9 @@
"""Shared validation helpers for KIE API modules."""
def _validate_prompt(prompt: str, *, max_length: int) -> None:
"""Ensure prompts are present and below the specified maximum length."""
if not prompt:
raise RuntimeError("Prompt is required.")
if len(prompt) > max_length:
raise RuntimeError(f"Prompt exceeds the maximum length of {max_length} characters.")