Skip to main content
Developers

Build with the QikTools API

Generate on-brand images and product video ads from your own code, Zapier or Make — with signed webhooks for long renders and the same flat credit prices as the app.

Included with the Studio plan · billed in credits · no extra fees.

curl -X POST https://www.qiktools.com/api/v1/generate \
  -H "Authorization: Bearer $QIKTOOLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "capability": "text-to-image",
        "prompt": "a green glass bottle on wet stone",
        "engine": "flash", "resolution": "2K" }'

{ "success": true,
  "imageUrl": "https://…/result.png",
  "creditsCharged": 6 }
REST + JSON

One endpoint, plain HTTPS. Works from any language, Zapier, Make or n8n.

Signed webhooks

Long renders run in the background and report back, HMAC-SHA256 signed.

Refunded on failure

Pay only for results: a failed generation's credits come straight back (the one exception is a provider-charged safety refusal).

Safe by design

Hashed keys, public-https webhooks only, per-account pacing and job caps.

In plain words

What is the QikTools API?

The QikTools API lets your own code, or an automation tool such as Zapier, Make or n8n, create on-brand AI images and product video ads. You send one HTTPS request with an API key; your Brand Vault is applied unless you switch it off for that request.

Each request is paid in credits at the same prices as the app, and the response says how many were charged. Long video renders run in the background and report back to your webhook. A failed generation’s credits come straight back, except when an AI provider’s own safety system refused the request and charged for it.

Key facts

Endpoint
POST /api/v1/generate
What it makes
Images and product video ads
Who can use it
Studio plan accounts
Billing
Credits, at the app's prices
Background jobs
Up to 5 at a time per account
Webhook deliveries
Signed, up to 3 attempts

Quick start

Three steps: create a key, send a request, use the result. The base URL is https://www.qiktools.com, requests and responses are JSON, and every request needs your key.
  1. Create a key under Settings → Developer (Studio plan). Copy it straight away — it's shown once. Keep it in an environment variable or secret manager, never in browser code or a public repository.
  2. Make a request. Images come back in the response; videos take minutes, so give them a webhook_url (or poll the job).
  3. Use the result. The response carries a hosted file URL — download what you need and store it yourself.

Generate an image

curl -X POST https://www.qiktools.com/api/v1/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capability": "text-to-image",
    "prompt": "a serene mountain lake at sunrise",
    "engine": "flash",
    "resolution": "2K",
    "aspectRatio": "16:9"
  }'

Response — 200 OK

{
  "success": true,
  "imageUrl": "https://…/image.png",
  "creditsCharged": 6,
  "settings": { "engine": "flash", "resolution": "2K", "aspectRatio": "16:9" }
}

Request builder

Choose what you want to make and see the exact price and the request that asks for it — no key needed. Prices come from the same code the API charges with.
What to make
Engine
Resolution
Aspect ratio
This request costs
6credits · about $0.12 on Studio

Charged when the request is accepted and refunded automatically if generation fails, except a provider-charged safety refusal (provider_safety_charged).

Your request

curl -X POST https://www.qiktools.com/api/v1/generate \
  -H "Authorization: Bearer $QIKTOOLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "capability": "text-to-image",
  "prompt": "a serene mountain lake at sunrise, editorial photo",
  "engine": "flash",
  "resolution": "2K",
  "aspectRatio": "16:9"
}'

Authentication

Send your secret key as a bearer token on every request:

Authorization: Bearer qt_live_…
  • Only a fingerprint of your key is stored, so it can't be shown again — if you lose it, revoke it and create a new one.
  • Revoke a key in Settings → Developer at any time; it stops working immediately.
  • Keys belong to a Studio account. If the account leaves the Studio plan, its keys stop working (403 plan_required).
  • Usage is charged to the workspace your account is working in — your personal credits, or your team's if you're on a team and have switched to its workspace (the workspace menu at the top of the site). There is never an automatic fallback to the other one; GET /api/v1/account tells you which one is in use.

Endpoints

Method & pathWhat it does
POST /api/v1/generateCreate a generation: an image, or a product video.
GET /api/v1/jobs/{job_id}Status and result of an asynchronous job.
GET /api/v1/accountYour plan and the credits a request would draw on — handy to test a key and check the balance.

Check your key and balance

curl https://www.qiktools.com/api/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY"

Response — 200 OK

{
  "plan": "studio",
  "credits": 1840,                 // the wallet requests are charged to
  "credits_source": "personal",    // "team" when that wallet belongs to your team
  "on_team": false
}

Models

Each engine value picks one AI model. Model names are written the way their makers write them.
engineModelCapabilityGood for
"standard"FLUX.1 [schnell]by Black Forest Labstext-to-imageFast drafts at one fixed size
"flash"Nano Banana 2.1by Googletext-to-imageSharp everyday images up to 4K
"pro"Nano Banana Proby Googletext-to-imageDetailed scenes and clear text up to 4K
"kling"Kling O1by Kuaishouproduct-commercial5 or 10 second product clips, with an optional voiceover
"seedance"Seedance 2.5by ByteDanceproduct-commercialLonger clips with native sound and spoken lines

Seedance 2.5 is reached through Runway's developer API. Model and company names are trademarks of their respective owners; QikTools is an independent service and isn't affiliated with, sponsored or endorsed by them.

Powered by Runway

Text-to-image

POST /api/v1/generate with "capability": "text-to-image". Finished in seconds, so the response is the image.
ParameterTypeDescription
capability
required
stringMust be "text-to-image".
prompt
required
stringWhat to draw. Up to 4,000 characters.
engine
optional
string
default "standard"
"standard" (FLUX.1 [schnell] — fast, fixed size), "flash" (Nano Banana 2.1) or "pro" (Nano Banana Pro) — higher detail and control. standard ignores resolution and aspectRatio. See Models.
resolution
optional
string
default "1K"
"1K", "2K" or "4K" — flash and pro only.
aspectRatio
optional
string
default "1:1"
16:9 1:1 9:16 21:9 4:3 3:2 5:4 4:5 3:4 2:3 1:4. Not every shape exists at every resolution (3:4 and 2:3 stop at 2K; 1:4 is 1K on flash only) — an unsupported combination is a 400 before anything is charged.
apply_brand
optional
boolean
default true
Apply your Brand Vault (voice, colors, exclusions). Send false to skip it for this request.
webhook_url
optional
stringOptional. Makes the call asynchronous — see Async jobs.
webhook_secret
optional
stringOptional, 8+ characters. Signs every webhook delivery.

Product commercial

POST /api/v1/generate with "capability": "product-commercial". Turns a product photo and a direction into a short video ad, optionally with a spoken voiceover. Renders take minutes.
ParameterTypeDescription
capability
required
stringMust be "product-commercial".
productImageUrl
required
string (https URL)A publicly reachable photo of the product. Up to 2,000 characters.
visualPrompt
required
stringHow the ad should look and move. Up to 4,000 characters.
voiceoverScript
optional
stringWhat the voice says. Up to 700 characters. On Kling O1 it's recorded and laid over the clip at no extra charge — keep it to about 2 words per second of video (a slightly long line is sped up a little to fit; a much longer one is cut at the end of the clip). Seedance 2.5 speaks it natively.
engine
optional
string
default "kling"
"kling" (Kling O1 — 5 or 10 seconds) or "seedance" (Seedance 2.5 — 4 to 30 seconds, choose the resolution). See Models.
durationSeconds
optional
number
default 5
Kling O1 snaps to 5 or 10. Seedance 2.5 is clamped to 4–30. The response's settings shows what was used.
resolution
optional
string
default "720p"
Seedance 2.5 only: "480p", "720p" or "1080p". Ignored for Kling O1.
aspectRatio
optional
string
default "9:16"
9:16 16:9 1:1 4:3 3:4 21:9 on Seedance 2.5. Kling O1 makes 9:16, 16:9 and 1:1 only — any other shape is refused with invalid_request (nothing is charged), never swapped.
apply_brand
optional
boolean
default true
Apply your Brand Vault. Send false to skip it for this request. A Seedance clip makes its own sound, so its prompt is sent exactly as you wrote it: a model that speaks could say the vault's words out loud.
webhook_url
optional
stringStrongly recommended for video — see Async jobs.
webhook_secret
optional
stringOptional, 8+ characters. Signs every webhook delivery.
Use a webhook for video. Without webhook_url the request stays open until the render finishes — minutes, which most HTTP clients and gateways will time out on. If your client gives up, the render may still complete and be charged; with a webhook you get an immediate 202 and can't lose track of it.

Async jobs & webhooks

Add a webhook_url and the call returns straight away with a job id; the work runs in the background and the result is delivered to your URL.

Start a video render

curl -X POST https://www.qiktools.com/api/v1/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capability": "product-commercial",
    "productImageUrl": "https://example.com/product.png",
    "visualPrompt": "The bottle turns slowly on a marble counter in soft morning light",
    "voiceoverScript": "Meet the bottle that keeps your water cold all day.",
    "engine": "seedance",
    "durationSeconds": 8,
    "resolution": "720p",
    "aspectRatio": "9:16",
    "webhook_url": "https://your-app.example.com/qiktools-webhook",
    "webhook_secret": "a-long-random-string-you-choose"
  }'

Response — 202 Accepted

{
  "job_id": "5b6f0d9c-…",
  "status": "processing",
  "capability": "product-commercial",
  "credits_charged": 207,
  "settings": { "engine": "seedance", "durationSeconds": 8, "resolution": "720p", "aspectRatio": "9:16" },
  "poll_url": "/api/v1/jobs/5b6f0d9c-…"
}

Prefer to poll?

Check on a job

curl https://www.qiktools.com/api/v1/jobs/JOB_ID \
  -H "Authorization: Bearer YOUR_API_KEY"

Job — 200 OK

{
  "job_id": "5b6f0d9c-…",
  "status": "succeeded",                 // "processing" | "succeeded" | "failed"
  "capability": "product-commercial",
  "result": { "video_url": "https://…/video.mp4", "has_voice": true },
  "error": null,                         // a message when status is "failed"
  "credits_charged": 207,                // 0 for a refunded failure; the kept credits for a provider_safety_charged refusal
  "created_at": "2026-09-25T12:00:00Z",
  "completed_at": "2026-09-25T12:03:41Z",
  "webhook": { "state": "delivered", "attempts": 1 }   // null if you gave no webhook_url
}

What your webhook receives

A POST with a JSON body, once the job finishes or fails:

Body

{
  "id": "delivery id",
  "event": "job.succeeded",              // or "job.failed"
  "created": 1767225600,
  "data": {
    "job_id": "5b6f0d9c-…",
    "status": "succeeded",               // or "failed" (with an "error" message)
    "capability": "product-commercial",
    "credits_charged": 207,
    "result": { "video_url": "https://…/video.mp4", "has_voice": true }
  }
}
HeaderValue
X-Qiktools-EventThe event name, e.g. job.succeeded.
X-Qiktools-DeliveryA unique id for this delivery (the same value as the body's id).
X-Qiktools-SignatureOnly if you sent a webhook_secret: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of "t.body" with your secret.

Verify the signature

import crypto from "node:crypto";

// Verify against the RAW request body, exactly as received (before any JSON parsing).
export function verifyQiktoolsWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((part) => part.split("=")));
  if (!parts.t || !parts.v1) return false;

  // Reject old deliveries so a captured request can't be replayed later.
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSeconds) return false;

  const expected = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express example — use the raw body for this route:
// app.post("/qiktools-webhook", express.raw({ type: "application/json" }), (req, res) => {
//   const ok = verifyQiktoolsWebhook(req.body.toString("utf8"), req.get("X-Qiktools-Signature") ?? "", process.env.QIKTOOLS_WEBHOOK_SECRET);
//   if (!ok) return res.sendStatus(400);
//   const event = JSON.parse(req.body.toString("utf8"));
//   // ...handle event.event ("job.succeeded" / "job.failed"), then:
//   res.sendStatus(200);
// });
Delivery rules. Your URL must be https:// and reachable on the public internet (private, internal and localhost addresses are refused, and redirects aren't followed). Respond with any 2xx within 10 seconds. Timeouts, network errors and 5xx/408/429 responses are retried up to 3 attempts in total; any other 4xx is treated as a refusal and not retried. Whatever happens, the result stays available from GET /api/v1/jobs/{job_id}, which also reports the delivery state, for 30 days after the job finishes (then the job is deleted and that address answers 404).

Safe retries (Idempotency-Key)

Send an Idempotency-Key header with POST /api/v1/generate and a retry can never charge you twice — after a timeout, a dropped connection or a crash on your side.

Generate with a key

# One key per generation (here a UUID). Retrying with the SAME key and body is always safe.
curl -X POST https://www.qiktools.com/api/v1/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f8c7e52-4c1b-4c8e-9a51-6d2f0b7a91c3" \
  -d '{ "capability": "text-to-image", "prompt": "a serene mountain lake at sunrise", "engine": "flash" }'
  • Make up one key per generation — a UUID is ideal (1 to 255 visible characters, no spaces) — and send the same key with every retry of that generation.
  • Same key, same body: you get the first request's answer again — the same image or video link, or the same job_id for an async call — with the header Idempotent-Replayed: true. Nothing is generated or charged again. The body is compared as JSON, so key order and spacing don't matter.
  • Same key, different body: refused with 422 idempotency_key_mismatch. Use a new key for a new request.
  • The first request is still running: 409 idempotency_key_in_use with a Retry-After header — ask again after that many seconds.
  • Keys belong to the API key that sent them (another key of yours can use the same value) and are kept for 24 hours; after that a key can be used again.
  • A request refused before anything was charged (a field error, too few credits, our safety checks refusing it) doesn't use up its key: fix the problem and retry with the same key. Once a request was charged, its answer is the key's answer — including a failure that was refunded — so retry a failed generation with a new key.
  • We keep a fingerprint of the body (never the body itself) and the answer we sent, for those 24 hours.

Pricing

Same flat credit prices as the in-app tools. The price is charged when the request is accepted and refunded automatically if generation fails, except a provider-charged safety refusal (provider_safety_charged); the response tells you exactly what was charged.

Text-to-image (per image)

Engine1K2K4K
standard · FLUX.1 [schnell] (fixed size)1——
flash · Nano Banana 2.14612
pro · Nano Banana Pro131326

Product commercial — Kling O1

LengthCredits
5 seconds49
10 seconds97

Product commercial — Seedance 2.5 (priced by length)

Resolution≈ per second4s5s8s10s15s
480p17.36987138173259
720p25.9104130207259388
1080p58.6235293469586879

Credits cost from $0.0185 (Teams and Studio annual) to $0.025 each. Any other length between 4 and 30 seconds is priced the same way: the whole clip, rounded up to a whole credit (about the per-second figure times the length).

Security best practices

Your key spends your credits, so treat it like a password. The API is built so a leaked key can do as little damage as possible — these habits close the rest.
Keep keys on the server

Store keys in environment variables or a secret manager. Never put one in browser code, a mobile app or a public repository — anyone who can read it can spend your credits.

One key per integration

Create a separate key for each app, Zap or script (up to 10). If one leaks, revoke just that one in Settings → Developer; the others keep working.

Watch “last used”

Settings → Developer shows when each key was last used. A key you don't recognise being used is a key to revoke straight away.

Verify every webhook

Set a webhook_secret and check the X-Qiktools-Signature header with the function above, rejecting deliveries older than 5 minutes. Webhook URLs must be public https addresses.

Keys are hashed, never stored

We keep only a SHA-256 hash of each key, so we can't show it again — copy it when it's created. Webhook URLs are never stored either, only their host.

Bounded by design

A key only works while the account is on the Studio plan, can never spend more than the wallet holds, runs at most 5 background jobs at once and is paced at 120 requests a minute.

Think a key has leaked? Revoke it now in Settings → Developer, then email hello@qiktools.com and we'll look at the account's recent activity with you.

Errors & limits

Every error is JSON: { "error": "message for a person", "code": "stable_identifier" }. Branch on code, show error.

StatuscodeWhat it means
400invalid_requestSomething in the request is wrong; the message says what. Nothing was charged.
401missing_api_keyNo Authorization: Bearer header.
401invalid_api_keyThe key is wrong or has been revoked.
402insufficient_creditsNot enough credits in the wallet this account is spending from (see GET /api/v1/account). Nothing was charged, and the other wallet is never touched.
402team_limit_reachedThis account spends its team's credits and has used the monthly allowance its team owner set. Nothing was charged; switch the account to Personal credits or ask the owner to raise the allowance.
403plan_requiredThe key's account is no longer on the Studio plan.
400content_blockedOur safety checks refused the request (see the AI & Acceptable Use Policy). Nothing was charged. Repeated refusals can restrict the account.
403media_blockedA file in the request was removed after a report and can't be used. Nothing was charged.
404not_foundNo such job on your account.
409idempotency_key_in_useA request with this Idempotency-Key is still running. Nothing more was charged; retry after the Retry-After seconds to get its answer.
409idempotency_key_unresolvedThe first request with this key was charged and finished, but its answer couldn't be kept. Nothing more was charged; check GET /api/v1/account and use a new key to run it again.
422idempotency_key_mismatchThis Idempotency-Key was already used with a different body. Nothing was charged; use a new key for a new request.
422provider_safety_chargedOur checks passed, but the model's provider refused the request under its own safety rules and charged for the attempt, so the credits are kept (charged: true) — see Terms → provider safety refusals. Change the request before retrying.
451region_blockedPaid AI isn't available from the country the request came from. Nothing was charged.
429too_many_active_jobsYou have 5 async jobs running. Retry after the Retry-After seconds.
413payload_too_largeThe request body is over 64 KB.
429rate_limitedMore than 120 requests a minute from one account, or several recent generations that didn't complete. Wait for the Retry-After seconds (also in retry_after_seconds).
500generation_failedThe generation didn't complete. Your credits were refunded automatically.
503screening_unavailableWe couldn't run our safety checks just now. Nothing was charged; retry shortly.
503idempotency_unavailableWe couldn't check your Idempotency-Key just now, so nothing was run or charged. Retry with the same key shortly.
503pausedQikTools has paused all new paid work for a short while. Nothing was charged; retry in a few minutes.
503tool_pausedThe tool behind this capability is paused for now — error says why and until when. Nothing was charged and the same Idempotency-Key can be used again; retry after the Retry-After seconds. Jobs that already started finish normally.
503label_failedThe video couldn't be given its visible AI-generated label in time, so it wasn't delivered. Your credits were refunded automatically; try again (an async job tries the label twice before it fails).
504time_budget_exceededA synchronous request ran past the time a single request is allowed. It was stopped and your credits were refunded. For long jobs, pass a webhook_url so the job runs asynchronously.
500internal_errorSomething went wrong on our side. Safe to retry a request that hadn't started a job.
  • Up to 5 async jobs can run at once per account.
  • A job still running after 20 minutes is failed and refunded automatically — you are never charged for output that doesn't arrive, except when the model's provider refused it under its own safety rules and charged for the attempt (provider_safety_charged; an async job then ends failed with that code and the credits kept).
  • Retrying after a timeout? Send the same Idempotency-Key as the first try and you'll never pay twice — see Safe retries.
  • Need higher limits or something we don't offer yet? Email hello@qiktools.com.

Zapier, Make & n8n

No code needed — any tool that can send an HTTP request can call the API.
  1. Add a Webhooks by Zapier → Custom Request step (Make: HTTP → Make a request; n8n: HTTP Request).
  2. Method POST, URL https://www.qiktools.com/api/v1/generate, body type JSON, and the body from the examples above.
  3. Headers: Authorization: Bearer YOUR_API_KEY and Content-Type: application/json.
  4. For video, set webhook_url to a Catch Hook trigger URL (Zapier: Webhooks by Zapier → Catch Hook; Make: Custom webhook) in a second scenario — it receives the finished video URL. Those triggers can't verify HMAC signatures, so leave webhook_secret out there and treat the URL itself as the secret.

Versioning

  • Every endpoint lives under /api/v1. Within v1 we only add things — new optional fields, new values and new response fields — so write integrations that ignore fields they don't recognise.
  • Anything that could break an integration ships as a new version, announced to key holders by email in advance.
  • Questions or a feature you need? Email hello@qiktools.com.

API questions

What can the QikTools API make?

Two things: AI images from a text prompt (text-to-image) and product video ads from a product photo (product-commercial), with an optional voiceover. Your Brand Vault is applied unless you send apply_brand: false.

How is the API billed?

In credits from your account, at the same prices as the app. Every response reports the credits charged, and a failed generation's credits are refunded automatically (the one exception is a provider-charged safety refusal).

Which plan includes API access?

The Studio plan. Create keys under Settings → Developer. If the account leaves the Studio plan, its keys stop working.

How do long video renders work?

Add a webhook_url and the request returns straight away with a job ID. The result is posted to your webhook (signed with HMAC-SHA256 when you set a webhook_secret), with up to 3 delivery attempts, or you can poll GET /api/v1/jobs/{id}. Each account can run up to 5 jobs at once; a job still running after 20 minutes is failed and refunded.

Can I retry a request without paying twice?

Yes. Send an Idempotency-Key header (a UUID per generation) and reuse it for that generation's retries: the same key with the same body returns the first answer, or the same job, and is never charged again. The same key with a different body is refused, and keys are kept for 24 hours per API key.

Can I call it from Zapier, Make or n8n?

Yes. It's plain HTTPS and JSON, so any tool that can send an HTTP request with an Authorization header can call it.