npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@apicity/kie

v0.11.0

Published

Kie provider for video and image generation (Kling 3.0, Grok Imagine, Omnihuman 1.5, Nano Banana Pro).

Readme

@apicity/kie

npm dependencies TypeScript

Kie provider for video and image generation (Kling 3.0, Grok Imagine, Omnihuman 1.5, Nano Banana Pro).

Runtime dependencies:

  • zod@^4.4.3 — request schemas attached to every POST endpoint as .schema

Installation

npm install @apicity/kie
# or
pnpm add @apicity/kie

Quick Start

import { createKie } from "@apicity/kie";

const kie = createKie({ apiKey: process.env.KIE_API_KEY! });

Resolve KIE_API_KEY only in server-side code or trusted job runners. Do not expose KIE API keys to browsers, mobile clients, public logs, or frontend bundles.

Media URLs

Request schemas validate the construction boundary and accept any string in media input fields — including local slugs such as @img-ref-1 or @asset/photo.png for assets that have not been uploaded yet. kie.ai fetches media from the public internet at task-creation time, so before calling createTask you must upload local assets and substitute publicly reachable URLs (e.g. https://example.com/image.png).

Omnihuman 1.5 model slug

KIE's Omnihuman 1.5 model uses the shared kie.post.api.v1.jobs.createTask endpoint. Send the omnihuman-1-5 model slug with a portrait image URL and a driving audio URL; KIE returns { code, msg, data: { taskId } }, and final video results are retrieved through kie.get.api.v1.jobs.recordInfo(taskId) or delivered to callBackUrl.

const task = await kie.post.api.v1.jobs.createTask({
  model: "omnihuman-1-5",
  input: {
    image_url: "https://example.com/portrait.png",
    mask_url: ["https://example.com/mask.png"],
    audio_url: "https://example.com/speech.mp3",
    prompt: "A person speaking naturally with gentle expressions.",
    output_resolution: "1080",
    pe_fast_mode: false,
    seed: -1,
  },
  callBackUrl: "https://example.com/api/callback",
});

image_url accepts JPEG, PNG, or WEBP portrait images up to 10 MB. mask_url is optional and accepts at most 5 subject mask image URLs. audio_url accepts MP3, WAV, AAC, OGG, or MP4 audio up to 10 MB and less than 60 seconds. Use output_resolution: "720" or "1080"; defaults are "1080" for resolution, false for pe_fast_mode, and -1 for a random seed.

Gemini Omni Character endpoint

Gemini Omni Character has its own direct endpoint: kie.post.api.v1.omni.character.create. Use it to create a reusable character reference for gemini-omni-video; the returned data.characterId can be passed in that model's character_ids array. If you already created voice traits through Gemini Omni Audio, pass those audio_ids to guide the character's tone or persona.

const character = await kie.post.api.v1.omni.character.create({
  descriptions: "A confident presenter in a blue blazer.",
  image_urls: ["https://example.com/presenter.png"],
  audio_ids: ["audio_01hx8p0demo"],
  character_name: "Presenter",
});

const video = await kie.post.api.v1.jobs.createTask({
  model: "gemini-omni-video",
  input: {
    prompt: "Presenter explains the product in a bright studio.",
    character_ids: [character.data!.characterId],
    duration: "4",
  },
});

The request field is descriptions (plural). image_urls is required and accepts exactly one public reference image, up to KIE's 20 MB upstream limit. See https://docs.kie.ai/market/gemini-omni-character for the full upstream contract.

Grok Imagine 1.5 model slugs

KIE's current Grok Imagine Quick Start markets Grok Imagine 1.5 through the existing grok-imagine/text-to-video and grok-imagine/image-to-video createTask slugs rather than a new stable grok-imagine-video-1-5 slug. The package keeps grok-imagine-video-1-5-preview for older recordings and callers, but new 1.5 integrations should start with the suite slugs shown at https://kie.ai/grok-imagine.

const textVideo = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine/text-to-video",
  input: {
    prompt: "A golden sunset over calm ocean waves",
    aspect_ratio: "16:9",
    mode: "normal",
    duration: 6,
    resolution: "480p",
    nsfw_checker: true,
  },
});

declare const referenceImageUrl: string;

const imageVideo = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine/image-to-video",
  input: {
    image_urls: [referenceImageUrl],
    prompt: "@image1 smiles and waves at the camera",
    aspect_ratio: "16:9",
    mode: "fun",
    duration: "6",
    resolution: "720p",
    nsfw_checker: true,
  },
});

For image-to-video, send either image_urls or task_id plus index from an earlier Grok image generation. Do not send both in one request. External image_urls must point to JPEG, PNG, or WEBP images; KIE's upstream limit is 7 images and 10 MB per image. mode: "spicy" is only available when sourcing the image from a previous Grok task_id, not from external image URLs. Active image-to-video prompts are capped at 4096 characters, and both Grok video models accept duration as either a JSON integer or a canonical decimal integer string from 6 through 30. Values such as 6 and "6" are forwarded in the representation supplied by the caller; Apicity does not coerce between them. Whitespace, signs, decimals, leading zeroes, and out-of-range values remain invalid. See KIE's current text-to-video and image-to-video sources and the repository's numeric-input compatibility audit for the evidence and field-specific decisions.

Grok Imagine Extend numeric contract

grok-imagine/extend requires a completed source task and preserves both contract fields exactly as supplied. extend_at is a required JSON number with minimum 0; fractional positions are accepted, omission is rejected, and Apicity does not inject KIE's advertised default. The extend_times field is the required string enum "6" | "10"; JSON numbers and malformed strings are rejected rather than coerced.

declare const completed480pTaskId: string;

const extended = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine/extend",
  resolution: "480p", // Pricing hint for the source resolution.
  input: {
    task_id: completed480pTaskId,
    prompt: "Continue the scene with a gentle camera drift.",
    extend_at: 2.5,
    extend_times: "6",
  },
});

The current contract comes from the repository's bounded compatibility matrix. See the numeric-input compatibility audit for the official-source conflict, historical observation, live results, and no-coercion decision.

ElevenLabs numeric contracts

The two KIE text-to-speech models share the same inclusive numeric contract:

| Models | Field | Minimum | Maximum | Default | | --- | --- | ---: | ---: | ---: | | elevenlabs/text-to-speech-multilingual-v2, elevenlabs/text-to-speech-turbo-2-5 | stability | 0 | 1 | 0.5 | | same | similarity_boost | 0 | 1 | 0.75 | | same | style | 0 | 1 | 0 | | same | speed | 0.7 | 1.2 | 1 |

For elevenlabs/text-to-dialogue-v3, stability accepts only the discrete values 0, 0.5, and 1, and defaults to 0.5.

Parsing with the exported Zod request schemas materializes these defaults in the parsed result. Calling kie.post.api.v1.jobs.createTask is intentionally different: it validates the request but serializes the original caller object, so omitted settings remain omitted on the wire. Explicit valid values are preserved in both direct parsing and transport.

See KIE's official pages for dialogue v3, multilingual v2, and turbo 2.5.

createTask returns { code, msg, data: { taskId } }. For production workloads, pass callBackUrl so KIE can notify you when the job finishes. Without a callback, poll kie.get.api.v1.jobs.recordInfo(taskId) until state is success or fail; successful responses carry generated media URLs in the resultJson string.

HappyHorse 1.1 model slugs

KIE exposes Alibaba HappyHorse 1.1 through the same shared kie.post.api.v1.jobs.createTask endpoint. Use the 1.1 model slugs happyhorse-1-1/text-to-video, happyhorse-1-1/image-to-video, and happyhorse-1-1/reference-to-video with the input shapes shown in KIE's docs at https://docs.kie.ai/market/happyhorse-1-1/text-to-video, https://docs.kie.ai/market/happyhorse-1-1/image-to-video, and https://docs.kie.ai/market/happyhorse-1-1/reference-to-video.

const textTask = await kie.post.api.v1.jobs.createTask({
  model: "happyhorse-1-1/text-to-video",
  input: {
    prompt: "A dog running on the earth",
    resolution: "1080p",
    aspect_ratio: "16:9",
    duration: 5,
  },
  callBackUrl: "https://example.com/api/callback",
});

const imageTask = await kie.post.api.v1.jobs.createTask({
  model: "happyhorse-1-1/image-to-video",
  input: {
    image_urls: ["https://example.com/first-frame.png"],
    prompt: "A cat running on the grass",
    resolution: "1080p",
    duration: 5,
  },
});

const referenceTask = await kie.post.api.v1.jobs.createTask({
  model: "happyhorse-1-1/reference-to-video",
  input: {
    reference_image: ["https://example.com/reference.png"],
    prompt: "A cat running on the grass",
    resolution: "1080p",
    aspect_ratio: "16:9",
    duration: 5,
  },
});

console.log(
  textTask.data?.taskId,
  imageTask.data?.taskId,
  referenceTask.data?.taskId,
);

For 1.1 image-to-video, image_urls is required and accepts exactly one first-frame image URL. For 1.1 reference-to-video, reference_image accepts 1-9 image URLs and prompts can refer to the images by position. All three generation modes use resolution ("720p" or "1080p") and integer duration from 3 to 15 seconds; text-to-video and reference-to-video also expose the wider 1.1 aspect-ratio set including "21:9" and "9:21".

Seedance 2 Mini createTask flow

Seedance 2 Mini also uses the shared KIE jobs endpoints. Submit model: "bytedance/seedance-2-mini" to kie.post.api.v1.jobs.createTask, then poll kie.get.api.v1.jobs.recordInfo(taskId) or pass callBackUrl for completion notifications.

const miniTask = await kie.post.api.v1.jobs.createTask({
  model: "bytedance/seedance-2-mini",
  input: {
    prompt: "A compact launch video with crisp product details.",
    reference_image_urls: ["https://example.com/product.png"],
    reference_video_urls: ["https://example.com/source.mp4"],
    reference_audio_urls: ["https://example.com/voice.wav"],
    generate_audio: false,
    resolution: "720p",
    aspect_ratio: "16:9",
    duration: 15,
    web_search: false,
    nsfw_checker: true,
  },
  callBackUrl: "https://example.com/api/kie-callback",
});

const miniInfo = await kie.get.api.v1.jobs.recordInfo(
  miniTask.data!.taskId
);
const miniResult = miniInfo.data?.resultJson
  ? JSON.parse(miniInfo.data.resultJson)
  : null;
console.log(miniInfo.data?.state, miniResult?.resultUrls);

The example explicitly sets duration: 15 as an upper-bound override. When duration is omitted, the exported schema applies its 5-second default.

prompt is optional and capped at 20000 characters. Media references default to empty arrays when using the exported Zod schema. duration is an integer from 4 to 15 seconds, resolution is 480p or 720p, and aspect_ratio is one of 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, or adaptive. The schema defaults are generate_audio: true, resolution: "720p", aspect_ratio: "16:9", duration: 5, web_search: false, and nsfw_checker: true.

Real-world example: Kling 3.0 Turbo createTask payloads

KIE's Kling 3.0 Turbo Quick Start exposes two createTask slugs: kling/v3-turbo-text-to-video and kling/v3-turbo-image-to-video. Both use the same kie.post.api.v1.jobs.createTask endpoint as the rest of the KIE media models; only the model and input block change.

import { createKie } from "@apicity/kie";

const kie = createKie({ apiKey: process.env.KIE_API_KEY! });

const textTask = await kie.post.api.v1.jobs.createTask({
  model: "kling/v3-turbo-text-to-video",
  input: {
    prompt: "A cinematic drone shot over glass towers at sunrise.",
    duration: 5,
    aspect_ratio: "16:9",
    resolution: "720p",
  },
});

const imageTask = await kie.post.api.v1.jobs.createTask({
  model: "kling/v3-turbo-image-to-video",
  input: {
    prompt: "Animate the product photo with a slow studio turntable move.",
    image_urls: ["https://example.com/product.png"],
    duration: 5,
    resolution: "1080p",
  },
});

console.log(textTask.data?.taskId, imageTask.data?.taskId);

The image-to-video shape accepts exactly one image_urls entry. Both Turbo shapes require prompt, numeric duration, and resolution (720p or 1080p); text-to-video additionally requires aspect_ratio (1:1, 9:16, or 16:9).

Real-world example: Kling 3.0 video with named element references

Kling 3.0/video has a feature most upstream video models lack — named element references. You upload reference images for each subject, give the subject a name and description, and refer back to it from the prompt with [name] placeholders. The result preserves identity across frames without prompt-engineering gymnastics.

The flow below is taken verbatim from tests/integration/kie-kling-30-reference-bakeoff.test.ts and replays against tests/recordings/kie_2079838932/kling-30-reference-bakeoff_875607413/recording.har, so the response shapes match what Kie actually returns.

import { readFileSync } from "node:fs";
import { createKie } from "@apicity/kie";

const kie = createKie({ apiKey: process.env.KIE_API_KEY! });

// 1. Upload each reference image. Kie returns a CDN-hosted
//    `downloadUrl` you'll thread through the job request — the bytes
//    themselves never live in the createTask payload.
async function upload(filename: string, mimeType: string): Promise<string> {
  const blob = new Blob([readFileSync(filename)], { type: mimeType });
  const res = await kie.post.api.fileStreamUpload({
    file: blob,
    filename,
    uploadPath: "images/test-uploads",
  });
  if (!res.data?.downloadUrl) throw new Error("upload failed");
  return res.data.downloadUrl;
}

// Two angles of the cat satisfy Kling's "2-4 images per element"
// minimum. The man only has one fixture, so we pass it twice.
const cat1  = await upload("cat1.jpg",  "image/jpeg");
const cat2  = await upload("cat2.jpg",  "image/jpeg");
const man   = await upload("man.jpg",   "image/jpeg");
const beach = await upload("beach.png", "image/png");

// 2. Submit the job. Each `kling_elements` entry binds a `name` to a
//    set of reference images; the prompt then refers to that subject
//    via `[name]`. `image_urls` carries general/setting references —
//    the beach plate here — that aren't tied to a named subject.
const task = await kie.post.api.v1.jobs.createTask({
  model: "kling-3.0/video",
  input: {
    prompt:
      "On a sandy beach with the ocean behind, [blue_suit_man] sits " +
      "cross-legged on the sand. [white_cat] climbs onto his lap, " +
      "purrs, and lifts a paw to bat playfully at his blue tie. " +
      "[blue_suit_man] smiles and waves at the camera with his free hand.",
    image_urls: [beach],
    kling_elements: [
      {
        name: "white_cat",
        description: "A white cat with mismatched yellow and blue eyes",
        element_input_urls: [cat1, cat2],
      },
      {
        name: "blue_suit_man",
        description: "A man wearing a blue suit and a blue tie",
        element_input_urls: [man, man],
      },
    ],
    sound: false,
    duration: "5",
    aspect_ratio: "16:9",
    mode: "std",
    multi_shots: false,
  },
});

if (!task.data?.taskId) throw new Error("createTask returned no taskId");
const taskId = task.data.taskId;
// → "56074f12319c68b246e5a03e05608f31"

// 3. Poll recordInfo until the job leaves the `generating` state.
//    Kie's terminal states are `success` and `fail` — anything else
//    (`waiting`, `queuing`, `generating`) means keep waiting.
let state: string = "waiting";
let resultJson: string | undefined;
for (let i = 0; i < 200; i++) {
  const info = await kie.get.api.v1.jobs.recordInfo(taskId);
  state = info.data?.state ?? "waiting";
  if (state === "success" || state === "fail") {
    resultJson = info.data?.resultJson;
    break;
  }
  await new Promise((r) => setTimeout(r, 10_000));
}
if (state !== "success" || !resultJson) throw new Error(`job ${state}`);

// 4. `resultJson` is a JSON-encoded string — parse it to get the
//    delivered media URLs. The shape is consistent across every kie
//    media model (single `resultUrls: string[]`).
const result = JSON.parse(resultJson) as { resultUrls: string[] };
console.log(result.resultUrls[0]);
// → "https://tempfile.aiquickdraw.com/k/56074f12319c68b246e5a03e05608f31_1_1777540551_7969.mp4"

Notes

  • kling_elements accepts at most 3 named subjects, and each element_input_urls array must hold 2–4 images. If you only have one reference per subject, repeat it (as the example does for man) to satisfy the minimum.
  • The poll loop watches for the terminal states success and fail — anything else (waiting, queuing, generating) means keep going. There's no check_after_secs hint as on some other providers, so a 10s cadence is conservative; in the recorded fixture the job completed after ~127s of generating (14 polls).
  • kie.post.api.v1.jobs.createTask is the unified entry point for every media model in this provider — Kling, Wan, Seedance, Grok Imagine, GPT-Image-2, Qwen2, and others. Swap the model and input block; the rest of the flow (upload → createTask → poll → parse resultJson) is identical.
  • For convenience, submitMediaJob(provider, request) and uploadFile(provider, blob, filename, uploadPath) re-export the same calls but throw KieError directly when the upstream envelope is missing the expected fields.
  • Errors surface as KieError with status, body, and an upstream code attached, so try { ... } catch (e) { if (e instanceof KieError) ... } gives you the upstream error directly.

Qwen2 image edit createTask flow

Qwen2 image edit uses the shared KIE jobs endpoint. Submit model: "qwen2/image-edit" to kie.post.api.v1.jobs.createTask, then poll kie.get.api.v1.jobs.recordInfo(taskId) or pass callBackUrl.

const task = await kie.post.api.v1.jobs.createTask({
  model: "qwen2/image-edit",
  callBackUrl: "https://your-domain.com/api/callback",
  input: {
    prompt: "Add sunglasses to the subject",
    image_url: "https://example.com/source.png",
    image_size: "16:9",
    seed: 0,
    output_format: "png",
    nsfw_checker: false,
  },
});

prompt is required and capped at 800 characters. image_url is a single source image URL, not file content; upload local images first and pass the returned URL. KIE accepts JPEG, PNG, and WEBP images up to 10 MB. image_size defaults to 16:9 and accepts 1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, and 21:9. output_format defaults to png and accepts jpeg or png. When supplied, seed must be an integer; an omitted seed remains absent, and KIE publishes no bounds or default. nsfw_checker defaults to false.

Qwen2 text-to-image createTask flow

Qwen2 text-to-image uses the same shared KIE jobs endpoint. Submit model: "qwen2/text-to-image" to kie.post.api.v1.jobs.createTask, then poll kie.get.api.v1.jobs.recordInfo(taskId) or pass callBackUrl.

const task = await kie.post.api.v1.jobs.createTask({
  model: "qwen2/text-to-image",
  callBackUrl: "https://your-domain.com/api/callback",
  input: {
    prompt: "A serene mountain landscape at sunrise",
    image_size: "16:9",
    seed: 0,
    output_format: "png",
    nsfw_checker: false,
  },
});

prompt is required and capped at 800 characters. image_size defaults to 16:9 and accepts 1:1, 3:4, 4:3, 9:16, and 16:9. output_format defaults to png and accepts jpeg or png; seed must be an integer when provided; nsfw_checker defaults to false.

Wan 2.2 auxiliary model operations

Wan 2.2 speech-to-video, image-to-video, animate move, and animate replace are four model operations on the same kie.post.api.v1.jobs.createTask endpoint, not four endpoint methods. Type the request at the package boundary, submit it, then read data.taskId and poll the shared kie.get.api.v1.jobs.recordInfo(taskId) endpoint.

import {
  createKie,
  type TaskResponse,
  type Wan22A14bImageToVideoTurboRequest,
  type Wan22A14bSpeechToVideoTurboRequest,
  type Wan22AnimateMoveRequest,
  type Wan22AnimateReplaceRequest,
} from "@apicity/kie";

const kie = createKie({ apiKey: process.env.KIE_API_KEY! });

const imageToVideo = {
  model: "wan/2-2-a14b-image-to-video-turbo",
  input: {
    image_url: "https://example.com/first-frame.png",
    prompt: "A slow camera push toward the subject",
  },
} satisfies Wan22A14bImageToVideoTurboRequest;

const speechToVideo = {
  model: "wan/2-2-a14b-speech-to-video-turbo",
  input: {
    prompt: "The presenter explains the product",
    image_url: "https://example.com/presenter.png",
    audio_url: "https://example.com/presenter.mp3",
  },
} satisfies Wan22A14bSpeechToVideoTurboRequest;

const animateMove = {
  model: "wan/2-2-animate-move",
  input: {
    video_url: "https://example.com/source.mp4",
    image_url: "https://example.com/subject.png",
  },
} satisfies Wan22AnimateMoveRequest;

const animateReplace = {
  model: "wan/2-2-animate-replace",
  input: {
    video_url: "https://example.com/source.mp4",
    image_url: "https://example.com/replacement.png",
  },
} satisfies Wan22AnimateReplaceRequest;

const task: TaskResponse = await kie.post.api.v1.jobs.createTask(imageToVideo);
const taskId = task.data?.taskId;
if (!taskId) throw new Error("KIE did not return a taskId");

const details = await kie.get.api.v1.jobs.recordInfo(taskId);
console.log(details.data?.state, details.data?.resultJson);

Use the same createTask call with speechToVideo, animateMove, or animateReplace for the other operations. The required media fields are public URLs; upload local assets before submission. A callBackUrl can be provided instead of polling. See the Wan 2.2 auxiliary-media evidence matrix for the exact documented fields, defaults, response envelope, and evidence boundary.

Qwen Image 3 createTask flow

Qwen Image 3 has four exact Kie model ids: qwen3/text-to-image, qwen3/image-to-image, qwen3/pro-text-to-image, and qwen3/pro-image-to-image. See the official model pages for qwen3/text-to-image, qwen3/image-to-image, qwen3/pro-text-to-image, and qwen3/pro-image-to-image.

Text-to-image accepts an optional resolution; there is no documented default, so set it explicitly when constructing a request:

const textTask = await kie.post.api.v1.jobs.createTask({
  model: "qwen3/text-to-image",
  input: {
    prompt: "A quiet alpine lake beneath the Milky Way",
    resolution: "2K",
    image_size: "16:9",
    output_format: "png",
    prompt_extend: true,
    nsfw_checker: false,
    seed: 1,
  },
});

Image-to-image uses the same shared fields plus image_urls, an array of one to three public source-image URLs. Its resolution defaults to 1K when omitted:

const imageTask = await kie.post.api.v1.jobs.createTask({
  model: "qwen3/pro-image-to-image",
  input: {
    prompt: "Turn this product photo into a watercolor illustration",
    image_urls: ["https://example.com/product.png"],
    image_size: "4:3",
    output_format: "jpeg",
    prompt_extend: true,
    nsfw_checker: false,
    seed: 1,
  },
});

Grok Imagine Image 2.0 createTask flow

Grok Imagine Image 2.0 has three exact Kie model ids: grok-imagine-image-2-0/text-to-image, grok-imagine-image-2-0/segment-map, and grok-imagine-image-2-0/image-edit. See the official model pages for grok-imagine-image-2-0/text-to-image, grok-imagine-image-2-0/segment-map, and grok-imagine-image-2-0/image-edit.

Text-to-image requires both a prompt and an aspect_ratio:

async function waitForSuccess(taskId: string): Promise<string> {
  for (let attempt = 0; attempt < 180; attempt++) {
    const details = await kie.get.api.v1.jobs.recordInfo(taskId);
    const state = details.data?.state ?? "waiting";
    if (state === "success") return details.data?.resultJson ?? "{}";
    if (state === "fail") throw new Error(`KIE task ${taskId} failed`);
    await new Promise((resolve) => setTimeout(resolve, 5_000));
  }
  throw new Error(`KIE task ${taskId} timed out`);
}

const textTask = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine-image-2-0/text-to-image",
  input: {
    prompt: "A white cat with mismatched yellow and blue eyes",
    aspect_ratio: "1:1",
  },
});
const sourceTaskId = textTask.data?.taskId;
if (!sourceTaskId) throw new Error("KIE did not return a taskId");
await waitForSuccess(sourceTaskId);

Segment-map consumes that source task. After polling the segment task to success, select an index of 1 or greater from its segments output and pass it to image-edit using Kie's upstream mask_indexs spelling:

const segmentTask = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine-image-2-0/segment-map",
  input: { task_id: sourceTaskId },
});
const segmentTaskId = segmentTask.data?.taskId;
if (!segmentTaskId) throw new Error("KIE did not return a taskId");

const segmentResultJson = await waitForSuccess(segmentTaskId);
const segmentResult = JSON.parse(segmentResultJson) as {
  resultObject?: { segments?: Array<{ index: number }> };
};
const maskIndex = segmentResult.resultObject?.segments?.find(
  ({ index }) => index >= 1,
)?.index;
if (maskIndex === undefined) throw new Error("No editable segment");

const editTask = await kie.post.api.v1.jobs.createTask({
  model: "grok-imagine-image-2-0/image-edit",
  input: {
    prompt: "Give the cat a red bow tie",
    task_id: sourceTaskId,
    mask_indexs: [maskIndex],
  },
});
console.log(editTask.data?.taskId);

API Reference

56 endpoints across 28 groups. Each method mirrors an upstream URL path.

chat

GET https://api.kie.ai/api/v1/chat/credit

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.chat.credit({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

claude

POST https://api.kie.ai/claude/v1/messages

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.claude.post.v1.messages({ /* ... */ });

Source: packages/provider/kie/src/claude.ts

codex

POST https://api.kie.ai/codex/v1/responses

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.codex.v1.responses({ /* ... */ });

Source: packages/provider/kie/src/responses.ts

flux

GET https://api.kie.ai/api/v1/flux/kontext/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.flux.kontext.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

gemini

POST https://api.kie.ai/gemini/v1/models/gemini-3-5-flash:streamGenerateContent

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini.post.v1.models.gemini35Flash.streamGenerateContent({ /* ... */ });

Source: packages/provider/kie/src/gemini.ts

POST https://api.kie.ai/gemini/v1/models/gemini-3-6-flash:streamGenerateContent

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini.post.v1.models.gemini36Flash.streamGenerateContent({ /* ... */ });

Source: packages/provider/kie/src/gemini.ts

POST https://api.kie.ai/gemini/v1/models/gemini-3-flash-v1betamodels:streamGenerateContent

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini.post.v1.models.gemini3FlashV1betamodels.streamGenerateContent({ /* ... */ });

Source: packages/provider/kie/src/gemini.ts

gemini25Flash

POST https://api.kie.ai/gemini-2.5-flash/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini25Flash.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-25-flash.ts

gemini25Pro

POST https://api.kie.ai/gemini-2.5-pro/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini25Pro.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-25-pro.ts

gemini31Pro

POST https://api.kie.ai/gemini-3.1-pro/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini31Pro.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-31-pro.ts

gemini35FlashOpenai

POST https://api.kie.ai/gemini-3-5-flash-openai/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini35FlashOpenai.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-35-flash-openai.ts

gemini36FlashOpenai

POST https://api.kie.ai/gemini-3-6-flash-openai/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini36FlashOpenai.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-36-flash-openai.ts

gemini3Flash

POST https://api.kie.ai/gemini-3-flash/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini3Flash.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-3-flash.ts

gemini3Pro

POST https://api.kie.ai/gemini-3-pro/v1/chat/completions

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.gemini3Pro.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/kie/src/gemini-3-pro.ts

generate

GET https://api.kie.ai/api/v1/generate/record-info?taskId={taskId}

Cost tier: prohibitive

const res = await kie.suno.get.api.v1.generate.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/add-instrumental

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.addInstrumental({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/add-vocals

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.addVocals({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/extend

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.generate.extend({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/generate-persona

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.generatePersona({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/get-timestamped-lyrics

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.getTimestampedLyrics({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/mashup

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.mashup({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/replace-section

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.replaceSection({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/sounds

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.generate.sounds({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/upload-cover

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.generate.uploadCover({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/generate/upload-extend

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.generate.uploadExtend({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

gpt4oImage

GET https://api.kie.ai/api/v1/gpt4o-image/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.gpt4oImage.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

grok

POST https://api.kie.ai/grok/v1/responses

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.grok.v1.responses({ /* ... */ });

Source: packages/provider/kie/src/responses.ts

jobs

GET https://api.kie.ai/api/v1/jobs/recordInfo?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.jobs.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

lyrics

GET https://api.kie.ai/api/v1/lyrics/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.lyrics.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/lyrics

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.lyrics({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

midi

GET https://api.kie.ai/api/v1/midi/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.midi.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/midi/generate

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.midi.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

mj

GET https://api.kie.ai/api/v1/mj/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.mj.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

mp4

GET https://api.kie.ai/api/v1/mp4/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.mp4.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/mp4/generate

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.mp4.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

responses

POST https://api.kie.ai/api/v1/responses

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.api.v1.responses({ /* ... */ });

Source: packages/provider/kie/src/responses.ts

runway

GET https://api.kie.ai/api/v1/runway/record-detail?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.get.api.v1.runway.recordDetail({ /* ... */ });

Source: packages/provider/kie/src/kie.ts

style

POST https://api.kie.ai/api/v1/style/generate

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.style.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

suno

GET https://api.kie.ai/api/v1/suno/cover/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.suno.cover.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/suno/cover/generate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.suno.cover.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

veo

GET https://api.kie.ai/api/v1/veo/get-1080p-video?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.veo.get.api.v1.veo.get1080pVideo({ /* ... */ });

Source: packages/provider/kie/src/veo.ts

GET https://api.kie.ai/api/v1/veo/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.veo.get.api.v1.veo.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/veo.ts

POST https://api.kie.ai/api/v1/veo/extend

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.veo.post.api.v1.veo.extend({ /* ... */ });

Source: packages/provider/kie/src/veo.ts

POST https://api.kie.ai/api/v1/veo/generate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.veo.post.api.v1.veo.generate({ /* ... */ });

Source: packages/provider/kie/src/veo.ts

POST https://api.kie.ai/api/v1/veo/get-4k-video

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.veo.post.api.v1.veo.get4kVideo({ /* ... */ });

Source: packages/provider/kie/src/veo.ts

vocalRemoval

GET https://api.kie.ai/api/v1/vocal-removal/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.vocalRemoval.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/vocal-removal/generate

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.vocalRemoval.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

voice

GET https://api.kie.ai/api/v1/voice/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.voice.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

GET https://api.kie.ai/api/v1/voice/validate-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.voice.validateInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/voice/check-voice

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.voice.checkVoice({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/voice/generate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.voice.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/voice/regenerate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.voice.regenerate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/voice/validate

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.post.api.v1.voice.validate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

wav

GET https://api.kie.ai/api/v1/wav/record-info?taskId={taskId}

Cost tier: prohibitive

Upstream docs ↗

const res = await kie.suno.get.api.v1.wav.recordInfo({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

POST https://api.kie.ai/api/v1/wav/generate

Cost tier: prohibitive

const res = await kie.suno.post.api.v1.wav.generate({ /* ... */ });

Source: packages/provider/kie/src/suno.ts

Middleware

import { createKie, withRetry } from "@apicity/kie";

const kie = createKie({ apiKey: process.env.KIE_API_KEY! });
const models = withRetry(kie.get.v1.models, { retries: 3 });

Part of the apicity monorepo.

License

MIT — see LICENSE.