List
GET / returns uploads and outputs for the current project, newest first. limit takes 1 to 200.
Per project
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.

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

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.
# 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"POST / 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.
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"| Format | Stored as kind | Limits |
|---|---|---|
| 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 |
Content-Type are ignored. GIF, HEIC and AVIF return 422 input.unsupported_format.https:// URLs and data: URIs as inputs and store them as assets for you.Idempotency-Key header and a retried upload returns the first asset instead of storing a second copy.Uploads and outputs share one shape. Outputs carry the job that made them; every asset carries when it expires.
{ "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" }}| Field | Meaning |
|---|---|
id | Use it in a job's inputs and with the asset endpoints. |
job_id | For outputs, the job that produced it. null for uploads. |
kind | image, video or audio today. |
source | upload, output, url or data_uri. |
url | The API URL for the bytes. Needs your key; also works in place of the id in inputs. |
expires_at | When the bytes will be deleted. null means kept until you delete it. |
deleted_reason | expired or deleted once the bytes are gone. |
Your key never needs to reach a browser. Download on your server, or hand the page a short-lived signed URL.
GET / returns uploads and outputs for the current project, newest first. limit takes 1 to 200.
Per project
GET / checks your key and redirects to a signed download URL valid for 15 minutes.
Server side
GET / returns that signed URL as JSON, ready for an <img> or <video> tag.
Browser ready, 15 min
# 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 } }Authorization header to the redirect target. curl -L, Python requests and httpx drop it when the redirect goes to another host.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.
| Op | Step | What 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. |
id into the next job's inputs.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).
| Setting | Value |
|---|---|
| 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) |
| Billed | Daily, per finished UTC day |
| Per request | retention_days can shorten it, never extend it |
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}'Delete one asset, or everything a finished job produced. Both return right away and remove the bytes.
# 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": [] } }409 generation.asset_in_use. The job-level delete lists those under skipped./content and /url return 410 generation.asset_gone, and using the asset as an input returns 422.deleted_at and deleted_reason, and its job still lists it. The bytes can't be recovered.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.
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.
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.
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.
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.
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.
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.
Request early access. We’ll email you when your account is approved, with 200 free credits to spend in the first 30 days.