One API, from code or an agent.

Call the REST API from your backend, or let a coding agent drive it through the hosted MCP server and agent skills. Same key, same credits, same projects.

A cute vintage tin toy robot in mint green on a pastel coral background, soft studio lighting, product photography

Z-Image Turbo · 1024 × 1024 · seed 11 · $0.004

Base URL
https://api.windpaint.ai/v1
Auth
Authorization: Bearer aak_…
OpenAPI
/v1/openapi.json
MCP
https://mcp.windpaint.ai/mcp

Pick a surface.

Every surface is a client of the same API, so jobs, assets and credits are shared between them.

Surfaces and their status today.
SurfaceWhat you getStatus
REST API
JSON over HTTPS at api.windpaint.ai/v1. The dashboard and every other surface are clients of it.
Available
MCP server
20 tools over streamable HTTP at mcp.windpaint.ai/mcp. Works with any MCP client that can send a custom header.
Available
Agent skills
Five SKILL.md files plus the MCP server, packaged as a plugin for Claude Code, Codex and Cursor.
Available
Python client
One standard-library file you copy into your project. Not on PyPI.
Preview
windpaint CLI
A single static binary with JSON output and stable exit codes, for scripts and agents with a shell.
First release pending

First request.

Send JSON with a bearer key. Add Prefer: wait and the API holds the response until the image is done, up to 60 seconds.

Generate an imagebash
curl -X POST https://api.windpaint.ai/v1/generation/capabilities/image.generate \  -H "Authorization: Bearer $WINDPAINT_API_KEY" \  -H "Content-Type: application/json" \  -H "Prefer: wait=30" \  -d '{"prompt": "a lighthouse at dusk", "aspect_ratio": "16:9"}'
202 Accepted, if it isn't done in timejson
{  "status": "queued",  "request_id": "f1e2d3c4-b5a6-4978-8a6b-5c4d3e2f1a0b",  "status_url": "https://api.windpaint.ai/v1/generation/requests/f1e2…/status",  "cancel_url": "https://api.windpaint.ai/v1/generation/requests/f1e2…/cancel",  "credits_estimate": "0.08"}
Conventions every endpoint follows.
TopicRule
VersioningEverything is under /v1. Additive changes ship there, so ignore fields you don't recognize.
BodiesJSON, validated strictly. An unknown field returns 400 request.validation_failed naming it.
ProjectsJobs and assets belong to a project: project_id in the body, else the X-Windpaint-Project header, else your default.
Ids and timesIds are UUIDs. Timestamps are ISO 8601 in UTC. Empty fields come back as null.
CreditsDecimal strings such as "612.40". Parse them with a decimal type, not a float.
Errors{"error": {code, message, details, request_id}}. Branch on code; quote request_id to support.

Four ways to get a result.

Generation runs as jobs. Images usually finish in seconds and video in minutes, so pick the pattern that fits the wait.

Wait on the submit

Send Prefer: wait and the API holds the response up to 60 seconds, then returns 200 with the outputs. If the job isn't done, you get the usual 202. Good for images.

Poll

GET the status_url until the status is completed, failed, nsfw or canceled. Start at 1 to 2 seconds and back off to about 15.

Stream

Server-sent events from /v1/generation/requests/{id}/stream: status, progress, log and done, with queue position. Workflow runs have their own stream.

Webhook

Pass webhook_url on the submit and get a signed POST when the job starts or finishes.

Stream a job's eventsbash
curl -N https://api.windpaint.ai/v1/generation/requests/$REQUEST_ID/stream \  -H "Authorization: Bearer $WINDPAINT_API_KEY"event: statusdata: {"status":"queued","request_id":"7c9e…","queue_position":2,"progress":null, ...}event: progressdata: {"request_id":"7c9e…","progress":0.5,"queue_position":null}

Browsers' EventSource can't send an Authorization header, so read the stream from your server and keep the key there.

Retry without paying twice.

A timeout on a submit leaves you not knowing whether the job exists. Send an idempotency key and retry with the same one.

The MCP server sends a random key on every submit, upload and workflow run, and retries the same call with it up to 3 times.

Submit with an Idempotency-Keybash
curl -X POST https://api.windpaint.ai/v1/generation/capabilities/image.generate \  -H "Authorization: Bearer $WINDPAINT_API_KEY" \  -H "Idempotency-Key: 4f1c2a9e-order-123" \  -H "Content-Type: application/json" \  -d '{"prompt": "a fox in the snow"}'# Same key, same body, within 24 hours:# the original response again, with Idempotent-Replayed: true
Submits, uploads and workflow runs accept the header. Keys last 24 hours.
SituationResponse
Same key and request, first one succeededThe original status and body, with Idempotent-Replayed: true. No new job, no new charge.
Same key, different body or project409 idempotency.key_reused.
Same key while the first is still processing409 idempotency.in_progress with Retry-After: 1. Retry with the same key.
Same key after the first failedRuns as new. Failed responses aren't stored, so fix the request and retry.

Signed and retried.

Deliveries follow the Standard Webhooks spec, so the official libraries verify them with your whsec_ secret as-is.

The webhook receiver recipe builds this out with storage, exactly-once downloads and a reconcile loop.

Verify a deliverypython
# pip install standardwebhooks fastapiimport osfrom fastapi import FastAPI, HTTPException, Requestfrom standardwebhooks.webhooks import Webhook, WebhookVerificationErrorwh = Webhook(os.environ["WINDPAINT_WEBHOOK_SECRET"])  # whsec_...app = FastAPI()@app.post("/hooks/windpaint")async def windpaint_hook(request: Request):    raw = await request.body()  # verify the raw bytes, not parsed JSON    try:        event = wh.verify(raw, dict(request.headers))    except WebhookVerificationError:        raise HTTPException(status_code=400)    # de-duplicate on request.headers["webhook-id"], act on event["data"]["status"]    return {"ok": True}
Events
started and completed. completed covers every final status, not only success.
Where
webhook_url on capability and model submits, and on workflow runs.
Signature
HMAC-SHA256 over the delivery id, timestamp and raw body, in webhook-signature.
Replays
Reject a webhook-timestamp more than 5 minutes from your clock.
Success
Any 2xx within 10 seconds. Redirects aren't followed.
Retries
Up to 10 attempts over about 6 hours, on timeouts, 408, 429 and 5xx.
Duplicates
webhook-id is the same on every retry of one event.
Delivery log
GET /v1/webhooks/jobs/deliveries shows every attempt and the exact body sent.
Job and workflow run webhooks.

Limits you can read.

Rate limits rise with lifetime credit purchases. Every authenticated response says where you stand.

Every tier also allows 1,200 requests per minute per API key. Free signup credits don't count toward a tier.
TierLifetime purchasesConcurrent jobsSubmits per minute
1
None (free credits only)
5
60
2
$50 or more
10
120
3
$500 or more
25
300
4
$2,000 or more
50
600

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over a limit you get 429 with Retry-After, and nothing is created or charged. GET /v1/billing/limits returns your tier and current usage.

Errors share one envelope with a stable error.code, such as billing.insufficient_credits. Jobs that end failed, nsfw or canceled carry an error_code, and GET /v1/errors lists every one with whether it's worth retrying. It needs no key.

Built for coding agents.

Point an agent at the hosted MCP server, or install the skills plugin and it connects the server for you. Agents read the live catalog and prices instead of guessing.

# Hosted MCP server (Claude Code shown; any MCP client works)
claude mcp add --transport http windpaint https://mcp.windpaint.ai/mcp \  --header "Authorization: Bearer $WINDPAINT_API_KEY"

MCP server

20 tools: catalog, estimate, upload, generate, wait, download and workflows. Auth is your API key in a header; there's no OAuth sign-in.

mcp.windpaint.ai/mcp

Skills plugin

Five skills that tell the agent to read prices from the API, estimate before spending, and keep waiting on a slow job instead of resubmitting it.

github.com/windpaint-ai/skills

CLI

JSON on stdout when piped, progress on stderr, and stable exit codes an agent can branch on. For agents that work on local files.

First release pending

The five skills in windpaint-ai/skills.
SkillUse it for
windpaintThe base: auth, the catalog, estimates, async jobs, assets, projects and balance. Loaded before the others.
windpaint-imageText to image with image.generate, variations and seeds.
windpaint-videoAnimating a start frame into a clip with video.generate, once a video model is live.
windpaint-workflowsRunning multi-step workflows such as text-to-clip, once they're available.
windpaint-apiWriting application code that calls the REST API. Doesn't need the MCP server.
  • Agents spend real credits. A job's price is held at submit, and a short balance is refused with 402 before anything runs.
  • The MCP server doesn't ask before generate or run_workflow. Keep your client's tool approval on for those if you want to approve each spend.
  • There are no per-key spending caps today. Your prepaid balance is the ceiling, and your tier's concurrency limit slows how fast an agent can spend.
  • Give each agent its own key, with an expiry, so you can revoke one without breaking the rest.

Spec and clients.

The OpenAPI 3.1 document is public. There's no packaged SDK yet, only a Python preview.

Client options today.
OptionStatus
OpenAPI 3.1 specPublic at /v1/openapi.json, no key needed. Request bodies are complete; most response bodies are untyped, and headers like Idempotency-Key and Prefer aren't in it. Use the reference for those.
Python clientPreview. One file, windpaint_client.py, standard library only, Python 3.10+. Not on PyPI; ask support for the current version. It doesn't send idempotency keys or retry.
Other languagesNo SDK. Generate a client from the spec, or call the API over HTTPS with any client.
Fetch the specbash
curl -s https://api.windpaint.ai/v1/openapi.json \  | jq '.info, (.paths | keys | length)'
Python client previewpython
import windpaint_client as wp  # preview, copied into your projectreq = wp.run("image.generate", prompt="a lighthouse at dusk", aspect_ratio="16:9")done = wp.wait(req["request_id"])wp.download(done["outputs"][0]["url"], "lighthouse.png")

Questions.

Is there an SDK?

Not a packaged one yet. There is a preview Python client: a single standard-library file, not on PyPI, that you copy into your project; ask support for the current version. Any language that speaks HTTPS works, and the public OpenAPI 3.1 spec can generate a client, though most response bodies in it are untyped.

Can I call the API from a browser or mobile app?

No. There are no publishable or browser-safe keys, and a key can spend your organization's credits. Call Windpaint from your backend and hand the client a signed asset URL, which is valid for 15 minutes.

How do I avoid paying twice when a submit times out?

Send an Idempotency-Key header on submits, uploads and workflow runs, and reuse it when you retry. Within 24 hours, the same key and request return the original response instead of creating and billing a second job.

Is the API versioned?

Every route lives under /v1. There is no version header and no dated versions; additive changes such as new fields, endpoints and enum values ship into /v1, so ignore fields you don't recognize.

What are the rate limits?

On tier 1 an organization can have 5 jobs in flight and make 60 submits per minute, and each API key can make 1,200 requests per minute. The tier rises with lifetime credit purchases. A request over a limit gets 429 with Retry-After, and nothing is created or charged.

Does the MCP server support OAuth?

No. The hosted server takes your API key in the Authorization header and stores nothing. Clients that can only connect through OAuth can't use it yet.

Do failed jobs cost credits?

No. A job's price is held from your balance when you submit it, and jobs that end failed, nsfw or canceled release the hold in full. You pay only for completed outputs.

Start yours.

Request early access. We’ll email you when your account is approved, with 200 free credits to spend in the first 30 days.