@apicity/kie
v0.1.0
Published
Kie provider for video and image generation (Kling 3.0, Grok Imagine, Nano Banana Pro).
Maintainers
Readme
@apicity/kie
Kie provider for video and image generation (Kling 3.0, Grok Imagine, Nano Banana Pro).
Installation
npm install @apicity/kie
# or
pnpm add @apicity/kieQuick Start
import { kie as createKie } from "@apicity/kie";
const kie = createKie({ apiKey: process.env.KIE_API_KEY! });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 { kie as 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_elementsaccepts at most 3 named subjects, and eachelement_input_urlsarray must hold 2–4 images. If you only have one reference per subject, repeat it (as the example does forman) to satisfy the minimum.- The poll loop watches for the terminal states
successandfail— anything else (waiting,queuing,generating) means keep going. There's nocheck_after_secshint 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.createTaskis the unified entry point for every media model in this provider — Kling, Wan, Seedance, Grok Imagine, GPT-Image-2, Qwen2, and others. Swap themodelandinputblock; the rest of the flow (upload → createTask → poll → parseresultJson) is identical.- For convenience,
submitMediaJob(provider, request)anduploadFile(provider, blob, filename, uploadPath)re-export the same calls but throwKieErrordirectly when the upstream envelope is missing the expected fields. - Errors surface as
KieErrorwithstatus,body, and an upstreamcodeattached, sotry { ... } catch (e) { if (e instanceof KieError) ... }gives you the upstream error directly.
API Reference
26 endpoints across 15 groups. Each method mirrors an upstream URL path.
chat
GET https://api.kie.ai/api/v1/chat/credit
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
const res = await kie.claude.post.v1.messages({ /* ... */ });Source: packages/provider/kie/src/claude.ts
common
POST https://api.kie.ai/api/v1/common/download-url
const res = await kie.post.api.v1.common.downloadUrl({ /* ... */ });Source: packages/provider/kie/src/kie.ts
fileBase64Upload
POST https://api.kie.ai/api/file-base64-upload
const res = await kie.post.api.fileBase64Upload({ /* ... */ });Source: packages/provider/kie/src/kie.ts
fileStreamUpload
POST https://api.kie.ai/api/file-stream-upload
const res = await kie.post.api.fileStreamUpload({ /* ... */ });Source: packages/provider/kie/src/kie.ts
fileUrlUpload
POST https://api.kie.ai/api/file-url-upload
const res = await kie.post.api.fileUrlUpload({ /* ... */ });Source: packages/provider/kie/src/kie.ts
generate
GET https://api.kie.ai/api/v1/generate/record-info?taskId={taskId}
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
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
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
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
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/mashup
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
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
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
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
const res = await kie.suno.post.api.v1.generate.uploadExtend({ /* ... */ });Source: packages/provider/kie/src/suno.ts
jobs
GET https://api.kie.ai/api/v1/jobs/recordInfo?taskId={taskId}
const res = await kie.get.api.v1.jobs.recordInfo({ /* ... */ });Source: packages/provider/kie/src/kie.ts
POST https://api.kie.ai/api/v1/jobs/createTask
const res = await kie.post.api.v1.jobs.createTask({ /* ... */ });Source: packages/provider/kie/src/kie.ts
lyrics
POST https://api.kie.ai/api/v1/lyrics
const res = await kie.suno.post.api.v1.lyrics({ /* ... */ });Source: packages/provider/kie/src/suno.ts
midi
POST https://api.kie.ai/api/v1/midi/generate
const res = await kie.suno.post.api.v1.midi.generate({ /* ... */ });Source: packages/provider/kie/src/suno.ts
mp4
POST https://api.kie.ai/api/v1/mp4/generate
const res = await kie.suno.post.api.v1.mp4.generate({ /* ... */ });Source: packages/provider/kie/src/suno.ts
style
POST https://api.kie.ai/api/v1/style/generate
const res = await kie.suno.post.api.v1.style.generate({ /* ... */ });Source: packages/provider/kie/src/suno.ts
veo
POST https://api.kie.ai/api/v1/veo/extend
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
const res = await kie.veo.post.api.v1.veo.generate({ /* ... */ });Source: packages/provider/kie/src/veo.ts
vocalRemoval
POST https://api.kie.ai/api/v1/vocal-removal/generate
const res = await kie.suno.post.api.v1.vocalRemoval.generate({ /* ... */ });Source: packages/provider/kie/src/suno.ts
wav
POST https://api.kie.ai/api/v1/wav/generate
const res = await kie.suno.post.api.v1.wav.generate({ /* ... */ });Source: packages/provider/kie/src/suno.ts
Middleware
import { kie as 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.
