Tài liệu

Tài liệu

Tạo hình ảnh

Hai endpoint: chuyển văn bản thành hình ảnh và chỉnh sửa hình ảnh / chuyển hình ảnh thành hình ảnh. Cả hai trả về URL kết quả vĩnh viễn: Kunavo lưu trữ mọi hình ảnh trên CDN riêng.

Kunavo cung cấp /v1/images/generations (chuyển văn bản thành hình ảnh) và /v1/images/edits (chuyển hình ảnh thành hình ảnh / chỉnh sửa). Theo mặc định, cả hai đều chạy đồng bộ — lệnh gọi chờ xử lý từ thượng nguồn hoàn tất rồi trả về URL kết quả — hoặc chạy bất đồng bộ bằng "async": true hoặc webhook_url.

Chuyển văn bản thành hình ảnh

Endpoint: POST /v1/images/generations. Dùng phương thức images.generate của OpenAI SDK hoặc gọi trực tiếp bằng curl.

img = client.images.generate(
    model="nano-banana-pro",
    prompt="a red origami crane on a white desk, soft window light",
    size="1024x1024",
)
print(img.data[0].url)

/v1/images/generations không nhận hình ảnh nào: yêu cầu có kèm hình ảnh (image, image_url hoặc image_urls) sẽ bị từ chối với lỗi 400. Để chỉnh sửa một hình ảnh, hãy gửi hình ảnh đó tới /v1/images/edits.

Các tham số thường dùng

Tham sốLoạiGhi chú
modelstringSlug của bất kỳ model hình ảnh nào trong /v1/models.
promptstringPrompt văn bản. Luôn bắt buộc.
sizestringĐịnh dạng kiểu OpenAI, "1024x1024". Được ánh xạ sang tỷ lệ khung hình của thượng nguồn.
qualitystandard|hdKhi được hỗ trợ (gpt-image).
resolution1K|2K|4KCấp độ phân giải cho các model phân cấp — xem Resolution tiers bên dưới. Tính phí theo cấp.
nintSố lượng hình ảnh. Hầu hết model chỉ trả về 1 hình ảnh.
inputobjectTham số mở rộng để bỏ qua giới hạn — xem Raw input bên dưới.
URL kết quả là liên kết vĩnh viễn trên files.kunavo.com: Kunavo lưu trữ mọi hình ảnh trên CDN riêng, vì vậy, khác với URL thô của nhà cung cấp thượng nguồn, các URL này không bao giờ hết hạn. Nếu một hình ảnh không thể được lưu trữ, URL của nó là URL tạm thời của nhà cung cấp thượng nguồn trên một máy chủ khác: hãy tải hình ảnh đó xuống và lưu trên máy chủ của riêng bạn.

Các cấp độ độ phân giải

Một số model hình ảnh tính giá theo từng mức dựa trên độ phân giải đầu ra. Truyền resolution để chọn cấp độ; nếu bỏ qua, cấp độ mặc định sẽ được dùng. Bạn sẽ bị tính phí theo cấp độ thực tế được tạo ra.

Model (và biến thể -edit của model)Các cấp độ — giá cho mỗi cấpMặc định
nano-banana-21K $0.0469 · 2K $0.0707 · 4K $0.10571K
nano-banana-pro1K / 2K $0.067 · 4K $0.122K
gpt-image-21K / 2K / 4K — $0.0633 (giá cố định)1K

Dòng veo-3 cũng phân cấp theo độ phân giải — xem giá theo từng cấp trên trang /models của mỗi model.

GET /v1/models trả về các cấp độ của từng model trong trường kunavo.tiers: giá niêm yết của mỗi cấp độ, và cấp độ được áp dụng cho yêu cầu nếu yêu cầu không chỉ định cấp độ.

Giá trị resolution nào không thuộc các cấp độ của model sẽ bị từ chối với lỗi 400, và việc gửi resolution cho model không có cấp độ phân giải cũng vậy.

GPT-Image-2 không được cung cấp ở mức 4K với tỷ lệ khung hình 1:1 (hình vuông). Yêu cầu đặt resolution = 4K sẽ bị từ chối với lỗi 400 khi kích thước là hình vuông hoặc khi yêu cầu không chỉ định kích thước nào. Hãy yêu cầu 2K, hoặc chỉ định một kích thước có tỷ lệ khung hình khác.

Chỉnh sửa hình ảnh / chuyển hình ảnh thành hình ảnh

Endpoint: POST /v1/images/edits. Truyền hình ảnh nguồn dưới dạng URL (HTTPS hoặc data: base64) qua image để chỉnh sửa một hình ảnh, hoặc qua image_urls để dùng nhiều hình ảnh tham chiếu.

# Image edit / image-to-image: pass the source image URL
import os

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]

resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue, keep composition",
        "image": "https://your-cdn.com/source.png",
    },
)
print(resp.json()["data"][0]["url"])

Tham chiếu nhiều hình ảnh

Một số model (Nano Banana Edit, GPT-Image-2 Edit) chấp nhận nhiều hình ảnh tham chiếu.

# nano-banana-edit accepts multiple reference images
resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "nano-banana-edit",
        "prompt": "merge subject from img1 with background from img2",
        "image_urls": [
            "https://your-cdn.com/subject.png",
            "https://your-cdn.com/background.png",
        ],
    },
)

Model nào nhận nhiều hình ảnh sẽ liệt kê multi-image-input trong GET /v1/models, và max_input_images của model đó là số hình ảnh tối đa mà một yêu cầu có thể gửi. Các model khác chỉ chỉnh sửa một hình ảnh trong mỗi yêu cầu. Yêu cầu nào gửi nhiều hình ảnh hơn số ảnh mà model nhận sẽ bị từ chối với lỗi 400.

Mặt nạ tô bù vùng ảnh (inpainting)

Một mô hình chỉnh sửa có capabilities bao gồm inpainting cũng chấp nhận mask: một hình ảnh mặt nạ, được truyền như hình ảnh nguồn bằng URL HTTPS hoặc dữ liệu base64 data:. Mặt nạ gửi đến bất kỳ mô hình chỉnh sửa nào khác sẽ bị từ chối với mã 400, mặt nạ mà Kunavo không đọc được cũng vậy.

Các model chỉnh sửa được hỗ trợ

  • nano-banana-edit — chỉnh sửa Google Nano Banana
  • gpt-image-2-edit — chuyển hình ảnh thành hình ảnh của OpenAI

Cung cấp hình ảnh nguồn

Các tính năng chỉnh sửa hình ảnh và chuyển hình ảnh thành video đều cần hình ảnh nguồn mà model có thể tải về. Bạn có ba lựa chọn — chọn cách phù hợp với ứng dụng của mình:

  • Bạn đã có URL công khai — truyền trực tiếp dưới dạng image (hoặc image_url cho video). Xong.
  • Bạn có tệp cục bộ — tải lên một lần qua POST /v1/files, rồi dùng lại URL cố định được trả về.
  • Bạn có dữ liệu thô trong mã — nhúng trực tiếp dưới dạng URI base64 data:; Kunavo lưu trữ chúng mà bạn không cần thao tác thêm.

Phương án A — tải lên qua POST /v1/files

Gửi multipart/form-data cùng trường file (hoặc { "file": "data:..." } dạng JSON). Bạn sẽ nhận được URL https cố định — áp dụng cho tệp có dung lượng tối đa 25 MB.

# Upload a local file → get a permanent public URL
import os

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]

with open("source.png", "rb") as f:
    up = requests.post(
        "https://api.kunavo.com/v1/files",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": f},
    ).json()

# up["url"] is now a permanent public URL for the image
edit = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue",
        "image": up["url"],
    },
).json()
print(edit["data"][0]["url"])

Phương án B — nhúng base64

Bỏ qua hoàn toàn bước tải tệp riêng: đặt URI data: vào trường image / image_url. Kunavo giải mã, lưu trữ trên CDN rồi cung cấp URL đã lưu trữ cho model.

# Or skip the upload step — pass base64 inline, we host it for you.
import base64
import os

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]

with open("source.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

resp = requests.post(
    "https://api.kunavo.com/v1/images/edits",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "gpt-image-2-edit",
        "prompt": "recolor the crane to bright blue",
        "image": f"data:image/png;base64,{b64}",
    },
)
Điều tương tự cũng áp dụng cho POST /v1/video/generations — image_url chấp nhận URL https hoặc URI base64 data: cho các model chuyển hình ảnh thành video như Veo.

Cấu trúc phản hồi

Cả hai endpoint đều trả về phản hồi hình ảnh tiêu chuẩn của OpenAI:

{
  "created": 1779360636,
  "data": [
    { "url": "https://files.kunavo.com/image/.../result.png" }
  ]
}

Độ trễ & thời gian chờ

Việc tạo hình ảnh thường mất 5–60 giây, tùy model. Ở chế độ đồng bộ mặc định, Kunavo giữ kết nối và thăm dò trạng thái thượng nguồn tối đa 540 giây trước khi trả về lỗi 504. Hãy đặt thời gian chờ phía máy khách cho phù hợp — hoặc dùng API bất đồng bộ để gửi yêu cầu trong vài giây rồi thăm dò theo lịch của bạn:

  • Nano Banana / Nano Banana 2: thường dưới 10 giây.
  • Nano Banana Pro / GPT-Image-2: 15–60 giây.
  • Tham chiếu nhiều hình ảnh + cấp cao cấp: tối đa khoảng 2 phút.

Hàm fetch tích hợp sẵn của Node ngừng chờ header phản hồi sau 300 giây, bất kể bạn đặt thời gian chờ là bao nhiêu: khi gọi từ Node, hãy dùng API bất đồng bộ, hoặc gọi hàm fetch từ gói undici với tùy chọn dispatcher: new Agent({ headersTimeout: 600_000 }).

API bất đồng bộ (gửi yêu cầu + thăm dò trạng thái / webhook)

Theo mặc định, cả hai endpoint hình ảnh đều chạy đồng bộ — lệnh gọi giữ kết nối cho đến khi thượng nguồn tạo xong hình ảnh, sau đó trả về phản hồi hình ảnh tiêu chuẩn của OpenAI. Với tác vụ hàng loạt, cấp cao cấp xử lý chậm hoặc ứng dụng gọi serverless không thể giữ yêu cầu lâu, hãy thêm "async": true (hoặc webhook_url) vào /v1/images/generations hoặc /v1/images/edits.

Yêu cầu gửi đi trả về ngay một mã img_*; hãy thăm dò GET /v1/images/{id} cho đến khi status là completed hoặc failed — hoặc đăng ký webhook để bỏ qua hoàn toàn việc thăm dò.

# Async: add "async": true (or a webhook_url) to either image endpoint.
# The call returns instantly with an img_ id instead of holding the connection.
import os
import time

import requests

API_KEY = os.environ["KUNAVO_API_KEY"]
API = "https://api.kunavo.com/v1"
H = {"Authorization": f"Bearer {API_KEY}"}

submit = requests.post(
    f"{API}/images/generations",
    headers=H,
    json={"model": "gpt-image-2", "prompt": "a red panda", "async": True},
).json()

task_id = submit["id"]        # "img_abc..."
status  = submit["status"]    # "queued"

# Poll GET /v1/images/{id} until terminal — or skip polling and use a webhook.
while status not in ("completed", "failed"):
    time.sleep(3)
    r = requests.get(f"{API}/images/{task_id}", headers=H).json()
    status = r["status"]

if status == "completed":
    print(r["output"]["url"])
else:
    print("failed:", r["error"])

Đối tượng tác vụ

TrườngLoạiGhi chú
idstringID tác vụ img_* — truyền vào GET /v1/images/{id}.
statusstringđang chờ | đang xử lý | đã hoàn tất | thất bại.
outputobject|nullKhi hoàn tất: { url, urls, archived }. archived là false khi một hình ảnh không thể được lưu trữ và URL của nó là URL tạm thời của nhà cung cấp thượng nguồn.
errorobject|nullKhi thất bại: { code, message }.
expires_atint|nullGiây Unix, 30 ngày sau khi gửi: có thể truy vấn tác vụ ít nhất cho đến lúc đó. URL kết quả của tác vụ không hết hạn.
GET /v1/images/{id} được giới hạn theo chủ sở hữu: truy vấn mã không thuộc về bạn sẽ trả về 404, không thể phân biệt với mã chưa từng được cấp. Tần suất thăm dò được đề xuất: 3 giây, dùng thời gian chờ tăng theo cấp số nhân lên đến khoảng 30 giây.

Cách thử lại an toàn

Với yêu cầu có "async": true hoặc webhook_url, cả hai endpoint hình ảnh đều đọc header Idempotency-Key dài tối đa 128 ký tự. Khi một yêu cầu được gửi lại với cùng một khóa, phản hồi là tác vụ mà yêu cầu đầu tiên đã tạo, không phải một hình ảnh thứ hai, chừng nào tác vụ đó còn có thể truy vấn: ít nhất 30 ngày.

Với khóa idempotency của một tác vụ đã thất bại, phản hồi vẫn là chính tác vụ đó: để thử lại, hãy gửi yêu cầu với một khóa idempotency mới. Yêu cầu không có "async": true cũng không có webhook_url sẽ không trả về tác vụ nào mà một lần gửi lại có thể tìm thấy, nên khóa idempotency được gửi kèm yêu cầu đó sẽ bị từ chối với lỗi 400.

  • Một khóa idempotency ứng với một yêu cầu: khóa đã được dùng để bắt đầu một tác vụ trên endpoint khác, cho model khác, hoặc cho một yêu cầu có prompt khác hay tham số khác sẽ bị từ chối với lỗi 400. Khóa dài hơn 128 ký tự và khóa được gửi kèm một yêu cầu đồng bộ cũng vậy. URL hình ảnh không được so sánh: yêu cầu dùng URL được ký lại vẫn là cùng một yêu cầu.

Webhook

Truyền webhook_url (https) khi gửi yêu cầu, và Kunavo sẽ POST một sự kiện image.completed / image.failed có chữ ký khi tác vụ kết thúc — cùng cấu trúc bao và chữ ký như webhook video và nhạc:

  • X-Kunavo-Webhook-Id — mã sự kiện ổn định, giống hệt nhau qua các lần thử lại (dùng để khử trùng lặp).
  • X-Kunavo-Webhook-Timestamp — thời điểm tính chữ ký, tính bằng giây Unix.
  • X-Kunavo-Webhook-Signature — HMAC-SHA256 của {timestamp}.{rawBody}, được tính bằng sha256= và khóa bí mật webhook của bạn.

Nếu webhook_url không phải là URL https, hoặc có chứa tên người dùng hay mật khẩu, yêu cầu sẽ bị từ chối với lỗi 400, và không có gì được tạo ra hay bị tính phí.

Khóa bí mật webhook của bạn nằm trong bảng điều khiển, tại Cài đặt → Webhooks. Khóa này bắt đầu bằng kwh_, và cùng một khóa bí mật được dùng để ký mọi sự kiện hình ảnh, video và nhạc của tài khoản bạn. Bạn có thể thay khóa tại đó; mọi sự kiện được gửi sau khi thay khóa, kể cả các lần thử lại, đều được ký bằng khóa bí mật mới.

Thử gửi lại với thời gian chờ tăng dần trong khoảng 9 giờ. Toàn bộ thời gian này, bản ghi bất đồng bộ vẫn có thể được thăm dò, nên nếu bỏ lỡ webhook, bạn luôn có thể khôi phục bằng GET.

Tham số mở rộng để truyền dữ liệu thô

Đối với các tham số riêng của từng model mà Kunavo không ánh xạ (seed, prompt phủ định, định dạng đầu ra), hãy truyền input. Giá trị này được gửi nguyên văn lên thượng nguồn, thay cho phần đầu vào mà bộ chuyển đổi của chúng tôi sẽ tạo.

Ngoại lệ là trường chọn cấp độ giá của model: resolution, hoặc rendering_speed đối với Ideogram. Với model có nhiều cấp độ, trường này luôn được gửi dưới dạng một trong các cấp độ của model và bạn sẽ bị tính phí theo cấp độ đó. Khi input không cung cấp trường này hoặc đưa ra giá trị không thuộc các cấp độ của model, cấp độ mặc định sẽ được gửi và tính phí. Nếu input được thiết lập, trường cùng tên ở cấp cao nhất của yêu cầu sẽ không được đọc.

# Raw upstream 'input': sent as written, in place of what our adapter builds.
# "resolution" sets the billed tier: 2K here, the default tier when left out.
resp = client.images.generate(
    model="nano-banana-2",
    prompt="",  # ignored when input is set
    extra_body={"input": {
        "prompt": "a futuristic cyberpunk skyline at dusk",
        "aspect_ratio": "16:9",
        "resolution": "2K",
        "output_format": "png",
    }},
)
Khi truyền input, bạn bỏ qua bước xác thực của Kunavo. Nếu thượng nguồn từ chối các trường, chúng tôi sẽ trả về lỗi 422 — hãy xem tài liệu lỗi và tài liệu thượng nguồn của model.

Tiếp theo nên xem gì