Every output, kept.

Uploads and generated outputs are stored as assets in your project. Download them, hand out signed URLs, feed them into the next job, and choose how long they stay.

A wall of tiny wooden display shelves each holding a different miniature diorama, a forest, a city, a beach, a snowy cabin, soft studio lighting
A wall of tiny wooden display shelves each holding a different miniature diorama, a forest, a city, a beach, a snowy cabin, soft studio…Z-Image Turbo · seed 11
Default retention
7 days
Range
1 to 365 days, or forever
First 7 days of each asset
Free
Past day 7
2 credits per GiB per 30 days ($0.10)

One id, start to finish.

Jobs read their inputs from assets and write their outputs as assets, so the id one step returns goes straight into the next.

id
goes straight into the next job
signed URL
valid for 15 minutes
7 days
of storage free, then 2 credits per GiB per 30 days
A hand-thrown ceramic vase with a glossy coral glaze holding dried pampas grass on a mint green plinth, soft daylight, minimal studio photography
A hand-thrown ceramic vase with a glossy coral glaze holding dried pampas grass on a mint green plinth, soft daylight, minimal studio…Z-Image Turbo · seed 11

Make something. Generate an image, or upload a file. Either way it's stored as an asset with an id, its kind, size, dimensions and an expires_at.

Make it yours. Pass the id to jobs that take inputs, with no download or re-upload. In a workflow, free steps resize a still and pull a cover frame from a clip, and each one is saved as its own asset.

Put it to work. List your assets, download the bytes, or mint a signed URL for a browser. Delete them when you're done, or let retention do it for you.

Generate, then servebash
# 1. Generate the image and keep its asset id.FRAME_ID=$(curl -s -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" \  -d '{"prompt": "a paper boat on a rain-soaked street, puddles, evening light",       "aspect_ratio": "16:9", "seed": 11}' | jq -r '.outputs[0].id')# 2. Mint a signed URL for a browser, valid for 15 minutes.curl -s https://api.windpaint.ai/v1/generation/assets/$FRAME_ID/url \  -H "Authorization: Bearer $WINDPAINT_API_KEY"

Bring your own files.

POST /v1/generation/uploads takes a multipart form with the file in a field named data. It lands in the project you pick with the X-Windpaint-Project header, or your default one.

Upload a filebash
curl -X POST https://api.windpaint.ai/v1/generation/uploads \  -H "Authorization: Bearer $WINDPAINT_API_KEY" \  -H "X-Windpaint-Project: launch-video" \  -F "data=@frame.png;type=image/png"
Accepted uploads
FormatStored as kindLimits
PNG, JPEG, WebP
image
20 MiB; long edge up to 8192 px, short edge at least 64 px
MP4, MOV, WebM
video
200 MiB; up to 60 seconds
MP3, WAV, M4A, OGG
audio
25 MiB; up to 5 minutes
  • The type is detected from the file's bytes; the extension and Content-Type are ignored. GIF, HEIC and AVIF return 422 input.unsupported_format.
  • Uploads are free. You pay for generation and, past the first 7 days, storage.
  • No upload needed for public files: jobs also take https:// URLs and data: URIs as inputs and store them as assets for you.
  • Send an Idempotency-Key header and a retried upload returns the first asset instead of storing a second copy.

What you get back.

Uploads and outputs share one shape. Outputs carry the job that made them; every asset carries when it expires.

GET /v1/generation/assets/{id}json
{  "data": {    "id": "8e1f2a3b-4c5d-4e6f-9a0b-1c2d3e4f5a6b",    "project_id": "5b0c7e1a-2f3d-4e5f-8a9b-0c1d2e3f4a5b",    "job_id": null,    "url": "https://api.windpaint.ai/v1/generation/assets/8e1f2a3b-4c5d-4e6f-9a0b-1c2d3e4f5a6b/content",    "kind": "image",    "source": "upload",    "content_type": "image/png",    "size_bytes": 422193,    "width": 1024,    "height": 576,    "duration_s": null,    "filename": "frame.png",    "source_url": null,    "expires_at": "2026-10-11T14:01:02.551Z",    "deleted_at": null,    "deleted_reason": null,    "created_at": "2026-10-04T14:01:02.551Z"  }}
Fields worth knowing
FieldMeaning
idUse it in a job's inputs and with the asset endpoints.
job_idFor outputs, the job that produced it. null for uploads.
kindimage, video or audio today.
sourceupload, output, url or data_uri.
urlThe API URL for the bytes. Needs your key; also works in place of the id in inputs.
expires_atWhen the bytes will be deleted. null means kept until you delete it.
deleted_reasonexpired or deleted once the bytes are gone.

Read it back.

Your key never needs to reach a browser. Download on your server, or hand the page a short-lived signed URL.

List

GET /v1/generation/assets returns uploads and outputs for the current project, newest first. limit takes 1 to 200.

Per project

Download

GET /v1/generation/assets/{id}/content checks your key and redirects to a signed download URL valid for 15 minutes.

Server side

Serve

GET /v1/generation/assets/{id}/url returns that signed URL as JSON, ready for an <img> or <video> tag.

Browser ready, 15 min

Download and signbash
# Download the bytes (follow the redirect to the signed URL)curl -L https://api.windpaint.ai/v1/generation/assets/$ASSET_ID/content \  -H "Authorization: Bearer $WINDPAINT_API_KEY" \  -o output.png# Get a URL for a browsercurl https://api.windpaint.ai/v1/generation/assets/$ASSET_ID/url \  -H "Authorization: Bearer $WINDPAINT_API_KEY"{ "data": { "url": "https://...signed...", "expires_in": 900 } }
  • Don't send your Authorization header to the redirect target. curl -L, Python requests and httpx drop it when the redirect goes to another host.
  • Reading an asset works anywhere in your organization. Using it as a job input works only in its own project.

New sizes, inside workflows.

There is no standalone transform endpoint yet. Resizing an image and pulling a frame from a video are free steps inside Windpaint workflows, and each result is saved as its own asset. Text to Clip, the first workflow, is coming soon.

Processing steps Text to Clip uses. Both are free.
OpStepWhat it does
image.resize
frame
Crops and resizes the still to the size the video model expects.
video.extract_frame
cover
Takes the last frame of the clip as a PNG, for a thumbnail or the start of the next clip.
  • Building your own pipeline? Chain generation jobs by passing one job's output asset id into the next job's inputs.

Kept as long as you need.

Every asset is kept for your organization's retention period, then its bytes are deleted. The setting applies to assets created after you change it.

Kept 7 days the default

Free. Deleted after day 7.

Kept 30 days 10 GiB

23 billed days, about 15.33 credits ($0.77).

Painted: the first 7 days of every asset, free. Ink: a billed day, at 2 credits per GiB per 30 days.
Retention and storage pricing
SettingValue
Default retention7 days
Range1 to 365 days, or forever
First 7 days of each assetFree
Past day 72 credits per GiB per 30 days ($0.10)
BilledDaily, per finished UTC day
Per requestretention_days can shorten it, never extend it
  • Keeping 10 GiB for 30 days instead of 7 is 10 GiB × 23 days, about 15.33 credits ($0.77). Assets are never deleted for non-payment.
Keep assets for 30 daysbash
curl -X PATCH https://api.windpaint.ai/v1/account/storage \  -H "Authorization: Bearer $WINDPAINT_API_KEY" \  -H "Content-Type: application/json" \  -d '{"asset_retention_days": 30}'

Gone when you say so.

Delete one asset, or everything a finished job produced. Both return right away and remove the bytes.

Delete assetsbash
# One asset: 204, bytes removed right awaycurl -X DELETE https://api.windpaint.ai/v1/generation/assets/$ASSET_ID \  -H "Authorization: Bearer $WINDPAINT_API_KEY"# Everything a finished job produced, and its inputscurl -X DELETE "https://api.windpaint.ai/v1/generation/requests/$REQUEST_ID/assets?inputs=true" \  -H "Authorization: Bearer $WINDPAINT_API_KEY"{ "data": { "deleted": ["d77a4c1e-..."], "skipped": [] } }
  • Deleting an asset a queued or running job uses as an input returns 409 generation.asset_in_use. The job-level delete lists those under skipped.
  • After expiry or deletion, /content and /url return 410 generation.asset_gone, and using the asset as an input returns 422.
  • The asset record stays readable with deleted_at and deleted_reason, and its job still lists it. The bytes can't be recovered.

Questions.

Is there an endpoint to resize or transform an image?

Not as a standalone endpoint yet. Resizing an image and pulling a frame from a video are free steps inside Windpaint workflows, which are coming soon, and each result is saved as its own asset. To build your own pipeline, chain generation jobs by passing one job's output asset id to the next.

How long are assets kept?

For your organization's retention period: 7 days by default. You can set it from 1 to 365 days or keep assets forever. A new setting applies to assets created after the change; existing assets keep the expiry they were created with.

What does storage cost?

The first 7 days of every asset are free, so a retention of 7 days or less never costs anything. Past that, storage is 2 credits per GiB per 30 days (about $0.10), charged once a day from your credit balance. Uploads and downloads are free.

How do I show an asset in a browser?

Fetch a signed URL from GET /v1/generation/assets/{id}/url on your server and hand it to the page. It works without a key for 15 minutes and can go straight into an img or video tag. Fetch a new one when it expires; for anything longer-lived, copy the bytes to your own storage.

Can I use an asset from another project?

You can read it: getting an asset, its bytes or a signed URL works anywhere in your organization. Using it as a job input only works inside the asset's own project, so download it and upload it into the other project.

What happens when an asset expires?

Its bytes are deleted within a few minutes of expires_at. Downloads then return 410 with generation.asset_gone, it drops out of the asset list, and using it as an input returns 422. Its metadata stays readable with deleted_at and deleted_reason set. Deletion waits if a running job still needs it.

Which file types can I upload?

PNG, JPEG and WebP images up to 20 MiB, MP4, MOV and WebM video up to 200 MiB and 60 seconds, and MP3, WAV, M4A and OGG audio up to 25 MiB and 5 minutes. The type is detected from the file's bytes. GIF, HEIC and AVIF are rejected.

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.