PR0TA asset downloading and export — download generated images, videos, and audio from PR0TA projects. Covers API download (preferred), browser gallery save, and bulk export. Read when saving assets, exporting clips, downloading videos, or troubleshooting 0-byte downloads. Includes the storage_uri fallback for video files and asset provenance tracking
See also: The
pr0ta-apiskill's reliability contract (reference/reliability-contract.md) covers the full download fallback chain — including thestorage_urivideo workaround, asset correlation rules, and the 0-byte detection pattern that this skill's API download section implements.
There are three ways to download assets from PR0TA: the API (preferred for automation), gallery context menus, and the Asset Browser.
PR0TA exposes a public download endpoint that does NOT require authentication. This is the preferred method when you have an asset ID, since it avoids the browser save dialog entirely.
curl via subprocess — NOT Python urllibField-tested reliability: PR0TA asset URLs sit behind Cloudflare, which 403s Python urllib.request.urlopen on asset downloads. requests works sometimes. curl invoked via subprocess works every time. Default to curl.
# ✅ RELIABLE — curl via subprocess
import subprocess
from pathlib import Path
def download_asset(project_id: str, asset_id: str, out_path: Path, pat: str | None = None) -> Path:
"""Download a PR0TA asset reliably via curl subprocess."""
url = f"https://app.pr0ta.com/api/v2/projects/{project_id}/assets/{asset_id}/download"
cmd = ["curl", "-sSL", "--fail", "-o", str(out_path), url]
if pat:
cmd.extend(["-H", f"Authorization: Bearer {pat}"])
subprocess.run(cmd, check=True)
# Validate byte count — 0-byte downloads should trigger storage_uri fallback
if out_path.stat().st_size == 0:
raise RuntimeError(f"0-byte download for asset {asset_id}")
return out_path
# ❌ DO NOT USE — Cloudflare will 403 this, often silently
import urllib.request
urllib.request.urlretrieve(url, out_path) # 403 Forbidden
Why: Cloudflare's bot-protection fingerprints urllib's default user-agent. requests passes sometimes but is inconsistent. curl with its default user-agent consistently passes the bot check. This is an environmental quirk of the CDN in front of PR0TA, not the API itself.
GET /api/v2/projects/{project_id}/assets/{asset_id}/download
This endpoint is public (no auth needed). Use curl from the shell or via subprocess:
curl -sSL --fail "https://app.pr0ta.com/api/v2/projects/{project_id}/assets/{asset_id}/download" \
-o output_filename.png
You can also request a specific size variant with ?size=thumbnail.
Status (April 2026 — hardened):
/download endpoint now works reliably for all asset types including video.result.asset_id, result.asset_ids, result.download_url, and result.urls on all succeeded generation tasks. Terminal-task asset reconciliation ensures tasks that reach succeeded without clean asset linkage are repaired from persisted assets. If result.asset_id is missing on a completed task, treat it as a platform bug and use the asset listing fallback. See pr0ta-api → "Task Polling" for the canonical response shape.Preferred download path after task completion:
# After polling task to "succeeded":
asset_id = task_data["result"]["asset_id"]
download_url = f"https://app.pr0ta.com{task_data['result']['download_url']}"
# OR construct from asset_id:
download_url = f"https://app.pr0ta.com/api/v2/projects/{project_id}/assets/{asset_id}/download"
The storage_uri auth fallback below is retained as defense-in-depth but should no longer be needed in normal operation.
If a video download ever returns 0 bytes, use the authenticated storage_uri fallback:
# 1) Get asset metadata to find storage_uri
curl "https://app.pr0ta.com/api/v2/projects/{project_id}/assets?kind=video" \
-H "Authorization: Bearer $PAT"
# 2) Read storage_uri from the asset object, then download with auth
curl -L -H "Authorization: Bearer $PAT" \
"https://app.pr0ta.com${storage_uri}" \
-o video_output.mp4
Important: The storage_uri path involves a redirect — always use curl -L (follow redirects) when downloading via this path.
The reliability contract in the pr0ta-api skill formalizes this as a two-step chain: try /download first, validate bytes (content-length > 0), then fall back to storage_uri if zero-byte. If both fail, the request is marked ambiguous for retry.
Asset IDs (UUIDs like 59cba82d-503d-4c4a-8f62-b48288895342) can be obtained by:
src attributes:
/api/v2/projects/{project_id}/assets/{asset_id}/download
These endpoints require a bearer token obtained via POST /api/auth/login:
List assets:
GET /api/v2/projects/{project_id}/assets
Supported query parameters: offset, limit, kind (image/video/audio), type, category, source, sort (asc/desc), sort_by, include_download, folder_path, recursive, music_only, is_imported
Get signed download URL:
GET /api/v2/projects/{project_id}/assets/{asset_id}/download-link
Returns a signed URL payload. Use ?as_attachment=true for download headers.
Get asset metadata:
GET /api/v2/projects/{project_id}/assets/{asset_id}/metadata
Browse asset facets:
GET /api/v2/projects/{project_id}/assets/facets
Authentication flow:
# 1) Get bearer token
TOKEN=$(curl -s -X POST https://app.pr0ta.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"password"}' | jq -r '.access_token')
# 2) List project assets
curl "https://app.pr0ta.com/api/v2/projects/PROJECT_ID/assets?kind=image&sort=desc&limit=10" \
-H "Authorization: Bearer $TOKEN"
# 3) Get signed download link
curl "https://app.pr0ta.com/api/v2/projects/PROJECT_ID/assets/ASSET_ID/download-link" \
-H "Authorization: Bearer $TOKEN"
curl -sL "https://app.pr0ta.com/api/v2/projects/{project_id}/assets/{asset_id}/download" \
-o desired_filename.ext
Each generator (Image, Video, Audio, Music) has a Gallery panel at the bottom showing previous generations. You can download any asset directly from here.
Note: This triggers the OS save dialog -- the user will need to confirm the save location.
The Asset Browser is a centralized asset management view for the entire project. It supports searching, filtering, multi-select, and bulk download.
Navigate to: Post-Production > Asset Browser or app.pr0ta.com/asset-library
Note: Downloads via the Asset Browser also trigger OS save dialogs.
app.pr0ta.com/asset-library)ref_181 in accessibility tree) in the toolbarstorage_uri fallback is available as defense-in-depth -- the public /download endpoint now works reliably (fixed April 2026), but the fallback is good practice. See the Video Download Fallback section above.src URLs which follow the pattern /api/v2/projects/{project_id}/assets/{asset_id}/downloadcurl downloads. For video batches, validate byte count on each download and retry via storage_uri for any 0-byte results.pr0ta-api skillassets.json + generation_contextDownloaded files land on disk with opaque UUID names. You need a way to answer "which prompt produced this file?" in under a minute, both during QC and after the project ships. There are two complementary sources of truth — use both:
1. Local assets.json (production-scoped, human-readable). Defined in the pr0ta hub skill, assets.json is the single canonical production ledger for any multi-shot job. It maps human-readable shot keys (img_title, vid_newsroom_01) to pr0ta_asset_id, local_path, source_prompt, model, used_in_shots, and any editorial notes. It is the file you read from when assembling the cut, and the file that ships with the final render. Append to assets.json immediately after every successful generation — not in a batch at the end of the production. See the pr0ta hub for the full schema.
2. API-side generation_context (authoritative fallback). As of April 2026, GET /api/v2/projects/{project_id}/assets/{asset_id} returns a generation_context block with prompt, model, negative_prompt, seed, task_id, submitted_at, completed_at, and status when recoverable. This is the source-of-truth fallback when your local assets.json is missing, stale, or lost. You don't need a second local ledger file — the API provides retrospective lookup for any asset you know the ID of.
Do not maintain a separate results.json ledger. Earlier versions of this skill prescribed one; it has been dropped to avoid duplication with assets.json. One local file + the API-side generation_context is the complete contract.