ZSky API + MCP

Developer API and MCP access: add a prepaid balance with no subscription — or use your Pro or Max plan. Both use the same generation pipeline as the web app: photographic image generation, video with synchronized audio, AI Creative Director output. Prepaid balance (no subscription): add $10, $25, $50 or $100, top up any time, and the balance never expires; each request draws it down at the same rates (see prepaid balance). Pro ($19 a month) is pay per use from the first second: video is billed per second of requested output by mode and resolution tier (Fast $0.10 per second in HD, $0.15 in Full HD, $0.20 in 2.5K; Quality $0.30 in HD, $0.50 in Full HD; Cinematic $0.70 in 2.5K), images $0.05 each ($0.08 above 1 MP), with a monthly cap you choose from $5 to $1,000. Max ($99 a month, or $79 a month billed annually) includes 3,000 video-seconds + 1,000 images per calendar month at any resolution (Cinematic seconds count double), then the same prices if you turn pay per use on. Allowances reset on the 1st of each calendar month, 00:00 UTC. See pricing and every limit.

Live on Max, Pro and prepaid balance. API and MCP share one API key, one monthly allowance, one pay-per-use cap, and the same limits and safety controls.

Authentication

The public API base URL is https://zsky.ai/api. Every API request and MCP connection uses a dedicated API key through the X-API-Key header. Create or rotate the key from Settings → API + MCP, or get one from the terminal with the quickstart. A rotated key is shown only once and immediately invalidates the previous key, so rotate only when you are ready to update every client that uses it.

curl https://zsky.ai/api/v1/usage \
  -H "X-API-Key: zsky_live_..." \
  -H "User-Agent: my-app/1.0"
Keep your key private. Anyone with your key can run generations against your account and consume your monthly allowance. Store in a secrets manager, never commit to source control, never expose in client-side code.

MCP setup

ZSky MCP is a local stdio adapter for compatible MCP clients, including Claude Code and Claude Desktop. It connects to the live ZSky API; it is not a separate hosted endpoint. The adapter requires Python 3.10 or newer. It starts without a key: the first tool call returns a payment path, zsky_start_access gives your agent one sign-in link to show you, and zsky_finish_access stores the key at ~/.zsky/api_token (readable only by you). The key is never shown in the chat.

  1. Download the package from Settings → API + MCP (Pro and Max accounts), or with your key: curl -OJ -H "X-API-Key: $ZSKY_API_TOKEN" -H "User-Agent: my-app/1.0" https://zsky.ai/api/v1/me/mcp-package (-OJ keeps the server's file name, which carries the current version).
  2. Install the downloaded zsky_mcp-*.whl package (below).
  3. Add zsky-mcp to your MCP client. A key in ZSKY_API_TOKEN is optional; without one, the adapter walks you through getting a key.
  4. Restart the MCP client, then call zsky_about or zsky_usage to confirm the connection.

Install the adapter

python3 -m venv .venv
.venv/bin/pip install ./zsky_mcp-*.whl

On Windows, in PowerShell, use .venv\Scripts\python -m pip install (Resolve-Path .\zsky_mcp-*.whl).

Claude Code

Without a key (the adapter gets one on first use):

claude mcp add zsky -s user -- /absolute/path/to/.venv/bin/zsky-mcp

With a key you already have:

claude mcp add zsky -s user \
  -e ZSKY_API_TOKEN=YOUR_ZSKY_API_KEY \
  -e ZSKY_MCP_MEDIA_ROOTS=/absolute/path/to/approved/media \
  -- /absolute/path/to/.venv/bin/zsky-mcp

ZSKY_MCP_MEDIA_ROOTS is optional for text-to-image and text-to-video. It is required for image-to-video and must name only directories containing images you want ZSky MCP to read. Supported inputs are PNG, JPEG, and WebP files up to 12 MiB.

Claude Desktop and other stdio clients

Add the following server to your MCP client configuration, using absolute paths:

{
  "mcpServers": {
    "zsky": {
      "command": "/absolute/path/to/.venv/bin/zsky-mcp",
      "env": {
        "ZSKY_API_TOKEN": "YOUR_ZSKY_API_KEY",
        "ZSKY_MCP_MEDIA_ROOTS": "/absolute/path/to/approved/media"
      }
    }
  }
}

Available tools are zsky_about, zsky_generate_image, zsky_generate_video, zsky_animate_image, zsky_check_status, zsky_usage, zsky_delete_output, zsky_start_access, and zsky_finish_access. Generation is asynchronous: generation tools return a job ID, and zsky_check_status returns progress and the signed result URL.

Shared allowance: API and MCP generations count against the same monthly allowance and the same pay-per-use cap. On Pro, every MCP generation is pay per use. Permanent deletion requires explicit confirmation through zsky_delete_output.

Endpoints

MethodPathPurpose
POST/v1/images/generateCreate an image (text-to-image)
POST/v1/videos/generateCreate a video (text-to-video)
GET/v1/jobs/<id>Poll job status, get signed result URL
DELETE/v1/jobs/<id>Purge the result from storage
GET/v1/usageCurrent-month allowance counters
GET/v1/me/overagePay-per-use state: on or off, monthly cap
POST/v1/me/overage/capSet the monthly cap (cap_cents, 500 to 100000)
POST/v1/me/overage/toggleTurn pay per use on or off
GET/v1/me/mcp-packageDownload the MCP package
POST/v1/pay/startStart a key claim (no key needed)
GET/v1/pay/claim/<code>Poll a claim; returns the key once when ready

POST /v1/images/generate

curl -X POST https://zsky.ai/api/v1/images/generate \
  -H "X-API-Key: zsky_live_..." \
  -H "User-Agent: my-app/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A neon-lit alley in Tokyo at dusk, photorealistic",
    "width": 1024,
    "height": 1024
  }'

# Response (HTTP 200):
{
  "job_id": "5f3...",
  "status": "queued",
  "kind": "image",
  "poll_url": "/api/v1/jobs/5f3..."
}

Parameters:

POST /v1/videos/generate

curl -X POST https://zsky.ai/api/v1/videos/generate \
  -H "X-API-Key: zsky_live_..." \
  -H "User-Agent: my-app/1.0" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A solitary lighthouse beam sweeping across choppy black sea, cinematic",
    "duration": 5
  }'

# Response (HTTP 200):
{
  "job_id": "8a1...",
  "status": "queued",
  "kind": "video",
  "poll_url": "/api/v1/jobs/8a1..."
}

Parameters:

Price: the seconds you request times the rate for the mode and the tier of the longer side of the delivered video (see pricing). The accepted response returns mode, output_width, output_height and price_tier, the tier you are billed at. The default 5-second 1280×720 Fast clip is $0.50 when pay per use applies.

Video generation is asynchronous. Use the poll_url to check status. For image-to-video, include init_image_base64 with the motion prompt. The MCP adapter handles this conversion through zsky_animate_image after validating that the local file is inside ZSKY_MCP_MEDIA_ROOTS.

GET /v1/jobs/<id>

curl https://zsky.ai/api/v1/jobs/8a1... -H "X-API-Key: zsky_live_..." -H "User-Agent: my-app/1.0"

# While queued/running:
{ "job_id": "8a1...", "status": "queued", "kind": "video" }

# When completed:
{
  "job_id": "8a1...",
  "status": "completed",
  "kind": "video",
  "result_url": "https://...r2.cloudflarestorage.com/...&X-Amz-Signature=...",
  "expires_in": 86400
}

# If blocked by safety:
{
  "job_id": "8a1...",
  "status": "blocked",
  "kind": "video",
  "error": "Sorry — our safety check stopped this video..."
}

result_url is a 24-hour signed download URL pointing directly at our storage. Download it once and persist locally if you need the bytes longer.

DELETE /v1/jobs/<id>

Purges the generated asset from our storage before the 7-day auto-delete. Useful when you've downloaded the bytes and want them gone immediately. Returns HTTP 204 on success, 404 if the job doesn't exist or isn't yours.

GET /v1/usage

curl https://zsky.ai/api/v1/usage -H "X-API-Key: zsky_live_..." -H "User-Agent: my-app/1.0"

# Response (Max key; video is metered in seconds, clip counts kept for older clients):
{
  "videos_used": 47,
  "videos_overage": 0,
  "images_used": 180,
  "images_overage": 0,
  "overage_charged_cents": 0,
  "base_videos_allowance": 300,
  "base_images_allowance": 1000,
  "video_seconds_used": 341,
  "video_seconds_overage": 0,
  "base_video_seconds_allowance": 3000,
  "rates": {
    "video_per_second_cents": {"hd": 10, "fhd": 15, "2k": 20},
    "default_video_mode": "fast",
    "video_modes": {
      "fast": {"per_second_cents": {"hd": 10, "fhd": 15, "2k": 20}, "max_included_weight": 1},
      "quality": {"per_second_cents": {"hd": 30, "fhd": 50}, "max_included_weight": 1, "durations_seconds": [5, 7, 10]},
      "cinema": {"per_second_cents": {"2k": 70}, "max_included_weight": 2, "durations_seconds": [5]}
    },
    "video_tiers": {"hd": "longer rendered side <= 1280", "fhd": "<= 1920", "2k": "<= 2560"},
    "image_cents": {"standard": 5, "large": 8},
    "image_sizes": {"standard": "<= 1,048,576 pixels (1024x1024)",
                    "large": "above 1,048,576 pixels, up to 2048x2048"},
    "max_video_seconds": 10,
    "render_grid_px": 64
  },
  "month_start": "2026-10-01"
}

API key management

Create or rotate the dedicated key from Settings → API + MCP, or from the terminal with the quickstart. Save it immediately: the plaintext value is shown only once, and the previous key is invalidated as soon as rotation succeeds.

Pay per use and your cap

The full list of limits is in Limits. On Pro, every API and MCP generation is pay per use and needs a monthly cap: choose one when you get your key ($10, $50, $250, or any amount from $5 to $1,000). A new account's cap is up to $50 for the first 7 days after its first payment, unless a $50 or $100 balance purchase confirmed with its bank has opened the full range. On Max, pay per use is off by default: a request past the included 3,000 video-seconds or 1,000 images stops with HTTP 429 until the monthly reset, unless you turn pay per use on and set a cap. A clip is included only if it fits whole in the included seconds you have left; otherwise the whole clip is billed at its tier rate and the seconds left stay available for shorter clips.

Pay-per-use pricing, per second of video, by mode and by the tier of the longer side of the delivered video: Fast $0.10 in HD (up to 1280 px), $0.15 in Full HD (up to 1920 px) and $0.20 in 2.5K (up to 2560 px); Quality $0.30 in HD and $0.50 in Full HD; Cinematic $0.70 in 2.5K. You are billed for the seconds you request (Fast 1 to 10 per clip; Quality 5, 7 or 10; Cinematic 5 today). Images: $0.05 up to 1,048,576 pixels (1024×1024) and $0.08 above that, up to 2048×2048. Fast output snaps to a 64-pixel grid (1920×1080 renders as 1920×1088); Quality and Cinematic deliver exact sizes, and a Quality Full HD square is delivered at 1080×1080 and billed HD. The response's price_tier is the tier you are billed at. No discount by length: the same rate applies to every second.

Worked examples
RequestTierPrice
5 s Fast video, 1280×720Fast HD, $0.10 per second$0.50
10 s Fast video, 1920×1080Fast Full HD, $0.15 per second$1.50
10 s Fast video, 2560×1440Fast 2.5K, $0.20 per second$2.00
5 s Quality video, 1920×1080Quality Full HD, $0.50 per second$2.50
5 s Cinematic video, 2560×1440Cinematic 2.5K, $0.70 per second$3.50
Image, 1024×1024Up to 1 MP$0.05
Image, 2048×2048Above 1 MP$0.08

A cap buys up to, in Fast HD: $10 = 100 video-seconds or 200 images; $50 = 500 or 1,000; $250 = 2,500 or 5,000. Every 429 that asks for money and GET /v1/usage carry the same table as data in rates. You are charged at month-end, on the card on your web subscription, for what you used, and never more than your cap in a calendar month. A plan bought in the App Store or Google Play (Pro or Max) cannot use pay per use; it needs a web subscription with a card on file.

Change the cap or turn pay per use on or off in Settings → API + MCP, or from the API with POST /v1/me/overage/cap and POST /v1/me/overage/toggle. A request that would pass your cap returns HTTP 429 with code: "monthly_cap_reached".

Prepaid balance

No subscription: add $10, $25, $50 or $100 on the claim page or from the terminal, and top up any time. Each API or MCP request draws the balance down at the prices above when it is accepted, and a request that fails or is blocked puts its amount back. A request the balance does not cover is refused with HTTP 402 balance_insufficient before anything runs; it is never billed later. Your balance never expires.

Optional auto-recharge adds the amount you choose ($10, $25, $50 or $100) with the card you paid with when the balance drops below $2, inside the monthly cap you set. If the card is declined, auto-recharge turns off, you get one email, and a request the balance does not cover returns 402 auto_recharge_declined: add to your balance with a working card (auto-recharge then uses that card). If you also have Pro or Max, pay per use draws the balance first (after Max’s included allowance) and the rest follows your plan’s month-end billing.

GET /v1/me/balance returns balance_cents, auto_recharge, your cap and your latest purchases, each with spent_cents and refundable_until. POST /v1/me/balance/auto-recharge with {"enabled": true, "amount_usd": 25} turns it on ({"enabled": false} turns it off). GET /v1/usage also carries balance_cents. A balance purchase can be refunded within 24 hours if none of it has been used. After that, or once any of it is spent, it is final. Your balance never expires. A $50 or $100 balance purchase may ask you to confirm with your bank; once confirmed, it opens the full limits right away: auto-recharge up to $100 and a monthly cap up to $1,000. Otherwise, auto-recharge adds up to $25 at a time until the card has been on your account for 7 days, and a new account's monthly cap is up to $50 for the first 7 days after its first payment.

Included allowance: no rollover. Max's 3,000 video-seconds and 1,000 images reset on the 1st of each calendar month (00:00 UTC), at any resolution. Unused included seconds and images do not carry over.

Storage & Retention

Safety

Every API request flows through the same safety pipeline as our web app: input checks on prompts, output checks on generated content. Blocked generations return status blocked with a human-readable reason. A blocked output is not charged and does not count against your allowance.

Repeated rejected requests (more than 50% of the last 30 minutes' attempts, minimum 10 events) trigger an automatic 24-hour suspension on the API key. If suspended, you'll get HTTP 403 until the window clears. Email support if you believe the suspension is in error.

Error Codes

StatusMeaning
200OK
204No content (DELETE success)
400Malformed request body or invalid params. new_account_cap_limit, new_account_recharge_limit or new_card_recharge_limit: the cap or auto top-up you asked for is above what this account or card can use yet; the body's limits object and raises_at say what is allowed now and when it rises. invalid_mode, mode_resolution_unsupported, mode_duration_unsupported or mode_input_unsupported: the mode cannot render this request; the body lists what it supports, and nothing is charged. With an uploaded image, Quality and Cinematic answer a prompt they cannot follow with a 400 prompt code.
401Missing or invalid API key. The body includes a payment object that starts the quickstart flow.
402payment_required: the account has not paid for API access (add a prepaid balance, or choose Pro or Max). balance_insufficient: the prepaid balance does not cover this request (the body has balance_cents, cost_cents and a top_up link); add to your balance or turn on auto-recharge. auto_recharge_declined: the card declined an auto-recharge and the balance does not cover this request; add to your balance with a working card. card_required: pay per use on Pro or Max needs a card on file from a web subscription. arrears: an earlier API invoice is unpaid; pay it in settings, then pay per use works again.
403API key suspended, or a request refused at the edge. Send a User-Agent that identifies your app; requests with a library's default User-Agent may be refused with 403. account_review_required: API payments on this account are paused; contact [email protected].
404Job not found (or not owned by you)
429Rate or daily limit hit (rate_limited, which also covers too many unrecognised keys from your network); quota_exhausted (included allowance used and pay per use off; for video the body names the video_mode and, on Max, the included_video_seconds_needed); monthly_cap_reached (the request would pass your cap); claims_per_client_limit (5 open sign-in codes from your network). Retry after the Retry-After header.
451Generation blocked by safety filter
500Internal error — please retry
503Temporary: workers unavailable, usage_unavailable, overage_temporarily_unavailable, billing_verification_unavailable, claims_busy, mode_unavailable (that mode has no capacity right now; Fast stays available), mode_temporarily_unavailable, mode_prompt_unavailable (Quality or Cinematic could not prepare a text prompt; try again or use Fast) or video_validation_unavailable. Retry after the Retry-After header.

Sample Python Client

#!/usr/bin/env python3
"""Minimal ZSky API client — generate an image, poll, download."""
import os, time, json, urllib.request

API = "https://zsky.ai/api"
KEY = os.environ["ZSKY_API_KEY"]

def call(method, path, body=None):
    req = urllib.request.Request(
        f"{API}{path}",
        data=json.dumps(body).encode() if body else None,
        # Send your own User-Agent: our edge rejects the default "Python-urllib" one.
        headers={"X-API-Key": KEY, "Content-Type": "application/json",
                 "User-Agent": "my-app/1.0"},
        method=method,
    )
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.loads(r.read())

def generate_image(prompt):
    job = call("POST", "/v1/images/generate", {"prompt": prompt})
    job_id = job["job_id"]
    while True:
        time.sleep(2)
        state = call("GET", f"/v1/jobs/{job_id}")
        if state["status"] in ("completed", "failed", "blocked"):
            return state

state = generate_image("A coffee cup on a wooden table, photorealistic")
if state["status"] == "completed":
    print(f"Result: {state['result_url']}")
    urllib.request.urlretrieve(state["result_url"], "out.webp")
else:
    print(f"Failed: {state.get('error')}")

Versioning & Stability

The v1 API surface is stable. Breaking API changes will ship under /v2. The current MCP adapter version is 2.1.3.

Support

Bugs, questions, and daily-limit lifts: write to [email protected] from your account email so we can verify ownership. Keys and the MCP package come from your account, not by email.

Get a key from the terminal

No key is needed to start. This works from any plan: the sign-in link lets you pick Pro (pay per use) or Max (included monthly allowance) if you are not on one yet.

# 1. start a claim (no key needed)
curl -s -X POST https://zsky.ai/api/v1/pay/start -H 'Content-Type: application/json' -H "User-Agent: my-app/1.0" -d '{"client":"cli"}'
# → open verify_url in a browser, sign in, confirm the code, and pick a plan (Pro or Max)
# 2. poll until status is "ready" (the key is shown once)
curl -s https://zsky.ai/api/v1/pay/claim/YOUR_CLAIM_CODE -H "User-Agent: my-app/1.0"
# 3. use it
curl -s -X POST https://zsky.ai/api/v1/images/generate -H "X-API-Key: zsky_live_..." -H 'Content-Type: application/json' -H "User-Agent: my-app/1.0" -d '{"prompt":"a lighthouse at dusk"}'

A 401 response includes a payment object with the same start URL, so an agent can begin this flow on its own and hand the link to you.

While you finish in the browser, the poll returns HTTP 202 with "status": "pending". The code expires after 15 minutes. After the key is delivered once, the poll returns 410.

Limits

LimitProMax
Included each monthNone: pay per use from the first second3,000 video-seconds and 1,000 images per calendar month (UTC), any resolution; Cinematic seconds count double; a clip is included only if it fits whole
Video price, per secondFast: $0.10 in HD (longer side up to 1280 px), $0.15 in Full HD (up to 1920 px), $0.20 in 2.5K (up to 2560 px). Quality: $0.30 in HD, $0.50 in Full HD. Cinematic: $0.70 in 2.5K. Max pays these only past the included seconds, when pay per use is on
Image price$0.05 up to 1,048,576 pixels (1024×1024); $0.08 above that, up to 2048×2048
Resolution tierBilled at the tier of the longer side of the delivered video. Fast snaps to a 64-pixel grid (1920×1080 renders as 1920×1088); Quality and Cinematic deliver exact sizes, and a Quality Full HD square is delivered at 1080×1080 and billed HD.
Monthly capRequired: $10, $50, $250, or any amount from $5 to $1,000 (with a prepaid balance only, optional: it bounds auto-recharge)Optional: $5 to $1,000
New accounts and cardsMonthly cap up to $50 for the first 7 days after the first payment; auto-recharge up to $25 until the card has been on the account for 7 days. A $50 or $100 balance purchase confirmed with your bank opens the full limits at once. The limits object on /v1/usage, /v1/me/overage and /v1/me/balance shows what applies now; paused or needs_support set to true means contact [email protected].
BillingPro and Max: at month-end, to the card on your web subscription, for what you used. Never more than your cap in a calendar month. Prepaid balance: drawn down as you generate; nothing is billed later.
Card on filePay per use on Pro or Max needs a card on file from a web subscription (a plan bought in the App Store or Google Play cannot turn pay per use on by itself). Max's included amount and a prepaid balance need no card.
Prepaid balanceNo subscription: add $10, $25, $50 or $100, top up any time, and the balance never expires. Each request draws it down at the prices above; a request the balance does not cover is refused (402 balance_insufficient), never billed later. Optional auto-recharge below $2, inside a monthly cap you set. No website plan comes with it.
What a cap buysUp to, in Fast HD: $10 = 100 video-seconds or 200 images; $50 = 500 or 1,000; $250 = 2,500 or 5,000
Monthly reset1st of each month, 00:00 UTC. Unused included seconds and images do not carry over.
Request rate100 image requests and 100 video requests per minute per account
Daily limit200 images and 50 videos per account per day; the count resets at midnight US Eastern time (ask support to raise it)
Concurrent jobs1 at a time per account (every key and sign-in on the account shares it)
Unrecognised keys20 requests a minute per network (an IPv4 address, or an IPv6 /64) with a key that matches no account, then HTTP 429 rate_limited. A valid key is never slowed by this.
Sign-in codesPOST /v1/pay/start: 10 a minute per network. Up to 5 waiting codes per network and 25 per wider network (an IPv4 /24 or an IPv6 /48); a new code replaces that network’s oldest code whose page was not opened. Each code lasts 15 minutes, and a code whose page was opened is kept for the full 15 minutes. If all 5 are open: HTTP 429 claims_per_client_limit with Retry-After.
Video length1 to 10 seconds per clip (10 seconds is the API maximum)
Output sizeImages 256 to 2,048 pixels per side; videos 256 to 2,560 pixels per side (2560 px is the API maximum)
UploadsImage-to-video source image up to 12 MB (PNG, JPEG, or WebP); lip-sync audio up to 10 MB; whole request up to 25 MB
PromptUp to 2,000 characters
ResultsSigned download link valid 24 hours; outputs kept 7 days

MCP calls count exactly like API calls: same key, same allowance, same cap, same limits.