> For the complete documentation index, see [llms.txt](https://docs.pletor.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pletor.ai/documentation/api-reference/assets.md).

# Assets

Assets are files you upload or that are generated through the API. They serve as inputs for runs (product photos, logos) and outputs produced by them (images, videos). Use these endpoints to upload, retrieve metadata, and download files across the full asset lifecycle — and to organize them with tags, bookmarks, archiving, and deletion.

Upload, retrieve, download, and organize files used as inputs or generated as outputs.

## List Assets

> List assets with pagination and optional filtering.\
> \
> This endpoint returns a paginated list of assets belonging to your organization,\
> newest first. You can optionally filter by a specific list of asset IDs, by the\
> flow or run that produced them, by tag, and by bookmark / archive state.\
> \
> \*\*What the default listing contains:\*\*\
> \- Without filters, the list holds the assets generated by your runs. Uploaded\
> &#x20; files are left out unless you pass \`tags\`, \`bookmarked\`, \`archived\` or\
> &#x20; \`asset\_ids\`, or look one up by \`external\_id\`.\
> \- Archived assets are hidden unless you pass \`archived\` or \`asset\_ids\`.\
> \- Deleted assets, and assets still being processed (a fresh upload or a\
> &#x20; just-generated output), are never listed. \`external\_id\` is the exception:\
> &#x20; it returns the asset in any state.\
> \
> \*\*Primary Use Cases:\*\*\
> \- Browse your generated assets with pagination\
> \- Retrieve specific assets using their IDs\
> \- Fetch every asset produced by a given flow across all its runs\
> \- Fetch every asset produced by a specific run\
> \- Verify the provenance of a known asset list (combine \`asset\_ids\` with\
> &#x20; \`flow\_id\` or \`run\_id\`)\
> \
> \*\*Pagination Parameters:\*\*\
> \- \`limit\`: Maximum number of items to return (default: 20, max: 100)\
> \- \`cursor\`: Cursor for pagination (optional)\
> \
> \*\*Filtering Parameters:\*\*\
> \- \`asset\_ids\`: Optional list of UUIDs to filter specific assets (repeatable).\
> \- \`flow\_id\`: Optional flow UUID. Returns all assets produced by that flow\
> &#x20; across every run.\
> \- \`run\_id\`: Optional run UUID. Returns all assets produced by that single run.\
> \- \`external\_id\`: Optional reference id you set at upload. Exact lookup that\
> &#x20; ignores every other filter.\
> \- \`tags\`: Optional tag names (repeatable). Returns assets carrying at least\
> &#x20; one of them (case-insensitive OR). Unknown tag names match nothing.\
> \- \`bookmarked\`: Optional boolean. Filters by bookmark state.\
> \- \`archived\`: Optional boolean. \`true\` shows only archived assets, \`false\`\
> &#x20; only non-archived. When omitted, archived assets are hidden (except with\
> &#x20; \`asset\_ids\`).\
> \- The filters compose as an AND. For example,\
> &#x20; \`?asset\_ids=\<id>\&flow\_id=\<flow>\` returns the asset only if it was\
> &#x20; produced by that flow; otherwise the response is empty.\
> \- \`flow\_id\` and \`run\_id\` only match assets that were produced by a flow\
> &#x20; run, so uploaded assets are excluded when either filter is set.\
> \
> \*\*Response:\*\*\
> Returns a \`CursorPaginatedResponse\` object containing:\
> \- \`data\`: List of \`AssetSummary\` objects. Each asset includes \`flow\_id\` and\
> &#x20; \`run\_id\` so you can correlate it back to the flow / run that produced it.\
> \- \`has\_more\`: Indicates if there are more results available\
> \- \`next\_cursor\`: Cursor for the next page\
> \
> \*\*Access Control:\*\*\
> \- Only assets belonging to your organization are returned, except that\
> &#x20; \`asset\_ids\` also returns \`public\` / \`shared\` assets from other\
> &#x20; organizations\
> \- Assets not found or not accessible are silently excluded\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 422: Invalid parameter (malformed UUID, \`limit\` outside 1-100)\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"CursorPaginatedResponse_AssetSummary_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AssetSummary"},"type":"array","title":"Data"},"has_more":{"type":"boolean","title":"Has More"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["data","has_more"],"title":"CursorPaginatedResponse[AssetSummary]"},"AssetSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique asset identifier"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Client-supplied reference id provided at upload, if any"},"asset_type":{"$ref":"#/components/schemas/AssetType","description":"Asset role: reference (a file you uploaded), intermediate (produced mid-run), or output (a run's final result)"},"media_type":{"$ref":"#/components/schemas/MediaType","description":"Media category: image, video, audio, file, or design (an HTML artboard, downloadable as a zip or a rendered image)"},"media_extension":{"$ref":"#/components/schemas/MediaExtension","description":"File extension of the stored file: png, jpg, mp4, pdf, etc."},"visibility":{"$ref":"#/components/schemas/AssetVisibility","description":"Visibility level: private (your organization only), or public / shared (readable by any authenticated caller that has the asset ID)"},"bookmarked":{"type":"boolean","title":"Bookmarked","description":"Whether the asset is bookmarked","default":false},"archived":{"type":"boolean","title":"Archived","description":"Whether the asset is archived. Also true for a deleted asset, which `GET /assets/{asset_id}` still returns","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag names applied to this asset, in their stored casing, sorted alphabetically (case-insensitive)"},"downloaded":{"type":"boolean","title":"Downloaded","description":"Whether a download link has ever been requested for this asset, through the API or the Pletor web app","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the asset was last modified"},"url":{"type":"string","title":"Url","description":"Signed URL to the full asset file. It expires (at most one hour after the response), so re-fetch the asset instead of storing it. While a generated asset is still processing, this is the provider's own short-lived URL"},"large_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large Url","description":"Stable CDN URL for a preview: a WebP rendition fitting 700x700 for images, a PNG poster frame for videos. Null for audio and other files. While an asset is still processing it is the provider's short-lived URL for images and null for videos"},"flow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Flow Id","description":"ID of the flow that produced this asset. Null for uploaded assets."},"run_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id","description":"ID of the run that produced this asset. Null for uploaded assets."},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Deprecated alias for `flow_id`, removed 31 December 2026.","deprecated":true,"readOnly":true}},"type":"object","required":["id","asset_type","media_type","media_extension","visibility","created_at","updated_at","url","agent_id"],"title":"AssetSummary","description":"Summary of an asset for list views."},"AssetType":{"type":"string","enum":["reference","intermediate","output"],"title":"AssetType","description":"Classification of assets by their role in workflows."},"MediaType":{"type":"string","enum":["image","video","audio","file","design"],"title":"MediaType","description":"Primary media categories for assets."},"MediaExtension":{"type":"string","enum":["png","jpg","jpeg","svg","heic","heif","avif","webp","gif","mp4","wav","mp3","mov","webm","pdf","docx","xlsx","pptx","csv","txt","json","md","html","css","js","xml","yaml","yml","zip","ai","eps","ttf","otf","woff","woff2"],"title":"MediaExtension"},"AssetVisibility":{"type":"string","enum":["public","shared","private"],"title":"AssetVisibility"},"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/":{"get":{"tags":["assets"],"summary":"List Assets","description":"List assets with pagination and optional filtering.\n\nThis endpoint returns a paginated list of assets belonging to your organization,\nnewest first. You can optionally filter by a specific list of asset IDs, by the\nflow or run that produced them, by tag, and by bookmark / archive state.\n\n**What the default listing contains:**\n- Without filters, the list holds the assets generated by your runs. Uploaded\n  files are left out unless you pass `tags`, `bookmarked`, `archived` or\n  `asset_ids`, or look one up by `external_id`.\n- Archived assets are hidden unless you pass `archived` or `asset_ids`.\n- Deleted assets, and assets still being processed (a fresh upload or a\n  just-generated output), are never listed. `external_id` is the exception:\n  it returns the asset in any state.\n\n**Primary Use Cases:**\n- Browse your generated assets with pagination\n- Retrieve specific assets using their IDs\n- Fetch every asset produced by a given flow across all its runs\n- Fetch every asset produced by a specific run\n- Verify the provenance of a known asset list (combine `asset_ids` with\n  `flow_id` or `run_id`)\n\n**Pagination Parameters:**\n- `limit`: Maximum number of items to return (default: 20, max: 100)\n- `cursor`: Cursor for pagination (optional)\n\n**Filtering Parameters:**\n- `asset_ids`: Optional list of UUIDs to filter specific assets (repeatable).\n- `flow_id`: Optional flow UUID. Returns all assets produced by that flow\n  across every run.\n- `run_id`: Optional run UUID. Returns all assets produced by that single run.\n- `external_id`: Optional reference id you set at upload. Exact lookup that\n  ignores every other filter.\n- `tags`: Optional tag names (repeatable). Returns assets carrying at least\n  one of them (case-insensitive OR). Unknown tag names match nothing.\n- `bookmarked`: Optional boolean. Filters by bookmark state.\n- `archived`: Optional boolean. `true` shows only archived assets, `false`\n  only non-archived. When omitted, archived assets are hidden (except with\n  `asset_ids`).\n- The filters compose as an AND. For example,\n  `?asset_ids=<id>&flow_id=<flow>` returns the asset only if it was\n  produced by that flow; otherwise the response is empty.\n- `flow_id` and `run_id` only match assets that were produced by a flow\n  run, so uploaded assets are excluded when either filter is set.\n\n**Response:**\nReturns a `CursorPaginatedResponse` object containing:\n- `data`: List of `AssetSummary` objects. Each asset includes `flow_id` and\n  `run_id` so you can correlate it back to the flow / run that produced it.\n- `has_more`: Indicates if there are more results available\n- `next_cursor`: Cursor for the next page\n\n**Access Control:**\n- Only assets belonging to your organization are returned, except that\n  `asset_ids` also returns `public` / `shared` assets from other\n  organizations\n- Assets not found or not accessible are silently excluded\n\n**Errors:**\n- 401: Missing or invalid API key\n- 422: Invalid parameter (malformed UUID, `limit` outside 1-100)\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_list_assets_assets__get","parameters":[{"name":"asset_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Restrict the result to this list of asset IDs (repeat the parameter to pass several). Combines with every other filter as an AND. Unlike the default listing, this lookup also returns uploads, archived assets (unless `archived=false`), and `public`/`shared` assets owned by other organizations.","title":"Asset Ids"},"description":"Restrict the result to this list of asset IDs (repeat the parameter to pass several). Combines with every other filter as an AND. Unlike the default listing, this lookup also returns uploads, archived assets (unless `archived=false`), and `public`/`shared` assets owned by other organizations."},{"name":"flow_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Return only assets produced by this flow (across all its runs). Uploaded assets are never returned by this filter.","title":"Flow Id"},"description":"Return only assets produced by this flow (across all its runs). Uploaded assets are never returned by this filter."},{"name":"agent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Deprecated alias for `flow_id`, removed 31 December 2026. Ignored when `flow_id` is also supplied.","deprecated":true,"title":"Agent Id"},"description":"Deprecated alias for `flow_id`, removed 31 December 2026. Ignored when `flow_id` is also supplied.","deprecated":true},{"name":"run_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Return only assets produced by this specific run. Uploaded assets are never returned by this filter.","title":"Run Id"},"description":"Return only assets produced by this specific run. Uploaded assets are never returned by this filter."},{"name":"external_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Return the asset you uploaded with this reference id. Use after a 409 on upload to fetch the existing asset. This is an exact lookup: all other filters are ignored when it is set, and the asset is returned whatever its state — still processing, archived, or deleted.","title":"External Id"},"description":"Return the asset you uploaded with this reference id. Use after a 409 on upload to fetch the existing asset. This is an exact lookup: all other filters are ignored when it is set, and the asset is returned whatever its state — still processing, archived, or deleted."},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Return only assets carrying at least one of these tag names (case-insensitive, OR semantics). Repeat the parameter to pass several: `?tags=summer&tags=hero`. Names that match no tag in your organization match nothing.","title":"Tags"},"description":"Return only assets carrying at least one of these tag names (case-insensitive, OR semantics). Repeat the parameter to pass several: `?tags=summer&tags=hero`. Names that match no tag in your organization match nothing."},{"name":"bookmarked","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true` returns only bookmarked assets, `false` only non-bookmarked ones. Omit to ignore the bookmark state.","title":"Bookmarked"},"description":"`true` returns only bookmarked assets, `false` only non-bookmarked ones. Omit to ignore the bookmark state."},{"name":"archived","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true` returns only archived assets, `false` only non-archived ones. Omit to hide archived assets (the default), except with `asset_ids`, which returns them unless this is `false`.","title":"Archived"},"description":"`true` returns only archived assets, `false` only non-archived ones. Omit to hide archived assets (the default), except with `asset_ids`, which returns them unless this is `false`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of items to return","default":20,"title":"Limit"},"description":"Maximum number of items to return"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination (from next_cursor)","title":"Cursor"},"description":"Cursor for pagination (from next_cursor)"},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CursorPaginatedResponse_AssetSummary_"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Get Asset

> Retrieve complete metadata for a specific asset.\
> \
> This endpoint returns detailed information about an asset, including its metadata,\
> access URLs, and processing information. Use this when you need complete asset\
> details rather than just summary information.\
> \
> Unlike the listing, this returns an asset in any state: one still being\
> processed, and one that has been deleted (it comes back with\
> \`archived: true\`, and its download answers \`410\`).\
> \
> \*\*Parameters:\*\*\
> \- \`asset\_id\`: UUID of the asset to retrieve\
> \
> \*\*Response:\*\*\
> Returns a complete \`Asset\` object with all \`AssetSummary\` fields plus:\
> \- \`asset\_metadata\`: Dictionary with processing metadata and technical details\
> \- \`is\_flow\_output\_sample\`: Whether this asset is a sample from a flow execution\
> \- \`blur\_hash\`: Hash for progressive image loading (images and videos)\
> \
> \*\*Access Control:\*\*\
> \- Asset must be owned by the authenticated user's organization, or\
> \- Asset must have "public" or "shared" visibility\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found or access denied\
> \- 422: \`asset\_id\` is not a valid UUID\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"Asset":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique asset identifier"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Client-supplied reference id provided at upload, if any"},"asset_type":{"$ref":"#/components/schemas/AssetType","description":"Asset role: reference (a file you uploaded), intermediate (produced mid-run), or output (a run's final result)"},"media_type":{"$ref":"#/components/schemas/MediaType","description":"Media category: image, video, audio, file, or design (an HTML artboard, downloadable as a zip or a rendered image)"},"media_extension":{"$ref":"#/components/schemas/MediaExtension","description":"File extension of the stored file: png, jpg, mp4, pdf, etc."},"visibility":{"$ref":"#/components/schemas/AssetVisibility","description":"Visibility level: private (your organization only), or public / shared (readable by any authenticated caller that has the asset ID)"},"bookmarked":{"type":"boolean","title":"Bookmarked","description":"Whether the asset is bookmarked","default":false},"archived":{"type":"boolean","title":"Archived","description":"Whether the asset is archived. Also true for a deleted asset, which `GET /assets/{asset_id}` still returns","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag names applied to this asset, in their stored casing, sorted alphabetically (case-insensitive)"},"downloaded":{"type":"boolean","title":"Downloaded","description":"Whether a download link has ever been requested for this asset, through the API or the Pletor web app","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the asset was last modified"},"url":{"type":"string","title":"Url","description":"Signed URL to the full asset file. It expires (at most one hour after the response), so re-fetch the asset instead of storing it. While a generated asset is still processing, this is the provider's own short-lived URL"},"large_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large Url","description":"Stable CDN URL for a preview: a WebP rendition fitting 700x700 for images, a PNG poster frame for videos. Null for audio and other files. While an asset is still processing it is the provider's short-lived URL for images and null for videos"},"flow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Flow Id","description":"ID of the flow that produced this asset. Null for uploaded assets."},"run_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id","description":"ID of the run that produced this asset. Null for uploaded assets."},"asset_metadata":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}]},"type":"object","title":"Asset Metadata","description":"Technical metadata: generation_model, aspect_ratio, duration, etc. Keys vary by media type and origin"},"is_flow_output_sample":{"type":"boolean","title":"Is Flow Output Sample","description":"Whether this asset is a sample output displayed on the flow card","default":false},"blur_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blur Hash","description":"BlurHash string for progressive image loading. Null until processing completes, and for audio and other files"},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Deprecated alias for `flow_id`, removed 31 December 2026.","deprecated":true,"readOnly":true}},"type":"object","required":["id","asset_type","media_type","media_extension","visibility","created_at","updated_at","url","agent_id"],"title":"Asset","description":"Complete asset with all metadata."},"AssetType":{"type":"string","enum":["reference","intermediate","output"],"title":"AssetType","description":"Classification of assets by their role in workflows."},"MediaType":{"type":"string","enum":["image","video","audio","file","design"],"title":"MediaType","description":"Primary media categories for assets."},"MediaExtension":{"type":"string","enum":["png","jpg","jpeg","svg","heic","heif","avif","webp","gif","mp4","wav","mp3","mov","webm","pdf","docx","xlsx","pptx","csv","txt","json","md","html","css","js","xml","yaml","yml","zip","ai","eps","ttf","otf","woff","woff2"],"title":"MediaExtension"},"AssetVisibility":{"type":"string","enum":["public","shared","private"],"title":"AssetVisibility"},"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}":{"get":{"tags":["assets"],"summary":"Get Asset","description":"Retrieve complete metadata for a specific asset.\n\nThis endpoint returns detailed information about an asset, including its metadata,\naccess URLs, and processing information. Use this when you need complete asset\ndetails rather than just summary information.\n\nUnlike the listing, this returns an asset in any state: one still being\nprocessed, and one that has been deleted (it comes back with\n`archived: true`, and its download answers `410`).\n\n**Parameters:**\n- `asset_id`: UUID of the asset to retrieve\n\n**Response:**\nReturns a complete `Asset` object with all `AssetSummary` fields plus:\n- `asset_metadata`: Dictionary with processing metadata and technical details\n- `is_flow_output_sample`: Whether this asset is a sample from a flow execution\n- `blur_hash`: Hash for progressive image loading (images and videos)\n\n**Access Control:**\n- Asset must be owned by the authenticated user's organization, or\n- Asset must have \"public\" or \"shared\" visibility\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found or access denied\n- 422: `asset_id` is not a valid UUID\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_get_asset_assets__asset_id__get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Delete Asset

> Soft-delete an asset.\
> \
> Marks the asset as deleted (and archived) so it stops appearing in listings\
> and can no longer be downloaded or modified: its download, \`PATCH\` and tag\
> endpoints answer \`410\`. The record is kept, so \`GET /assets/{asset\_id}\` and\
> \`GET /assets?external\_id=\` still return it, with \`archived: true\`. Its\
> \`external\_id\` stays claimed. There is no API to restore a deleted asset.\
> An asset still being processed can be deleted.\
> \
> \*\*Parameters:\*\*\
> \- \`asset\_id\`: UUID of the asset to delete\
> \
> \*\*Access Control:\*\*\
> \- Asset must be owned by the authenticated user's organization. Assets from\
> &#x20; other organizations are reported as \`404\` rather than deleted.\
> \
> \*\*Response:\*\*\
> \- \`204 No Content\` on success\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found or access denied\
> \- 410: Asset has already been deleted\
> \- 422: \`asset\_id\` is not a valid UUID\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}":{"delete":{"tags":["assets"],"summary":"Delete Asset","description":"Soft-delete an asset.\n\nMarks the asset as deleted (and archived) so it stops appearing in listings\nand can no longer be downloaded or modified: its download, `PATCH` and tag\nendpoints answer `410`. The record is kept, so `GET /assets/{asset_id}` and\n`GET /assets?external_id=` still return it, with `archived: true`. Its\n`external_id` stays claimed. There is no API to restore a deleted asset.\nAn asset still being processed can be deleted.\n\n**Parameters:**\n- `asset_id`: UUID of the asset to delete\n\n**Access Control:**\n- Asset must be owned by the authenticated user's organization. Assets from\n  other organizations are reported as `404` rather than deleted.\n\n**Response:**\n- `204 No Content` on success\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found or access denied\n- 410: Asset has already been deleted\n- 422: `asset_id` is not a valid UUID\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_delete_asset_assets__asset_id__delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Update Asset

> Update an asset's organization flags.\
> \
> Use this endpoint to bookmark/unbookmark and archive/unarchive an asset.\
> Fields omitted from the request body, or sent as \`null\`, are left unchanged;\
> an empty body returns the asset as it is.\
> \
> \*\*Request body:\*\*\
> \- \`bookmarked\`: Optional boolean. \`true\` bookmarks the asset, \`false\`\
> &#x20; removes the bookmark.\
> \- \`archived\`: Optional boolean. \`true\` archives the asset (it disappears\
> &#x20; from default listings; retrieve it with \`GET /assets?archived=true\`),\
> &#x20; \`false\` unarchives it.\
> \
> \*\*Response:\*\*\
> Returns the complete updated \`Asset\` object.\
> \
> \*\*Access Control:\*\*\
> \- Asset must be owned by the authenticated user's organization. Assets from\
> &#x20; other organizations are reported as \`404\` rather than updated.\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found or access denied\
> \- 409: \`archived: true\` was sent while the asset is still being processed —\
> &#x20; retry once processing has completed (bookmarking is allowed meanwhile)\
> \- 410: Asset has been deleted\
> \- 422: Invalid request body (a field is not a boolean) or malformed \`asset\_id\`\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"AssetUpdateRequest":{"properties":{"bookmarked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Bookmarked","description":"Set to true to bookmark the asset, false to remove the bookmark. Omit or send null to leave it unchanged"},"archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Archived","description":"Set to true to archive the asset, false to unarchive it. Omit or send null to leave it unchanged. Archiving an asset still being processed returns 409"}},"type":"object","title":"AssetUpdateRequest","description":"Fields an API client can update on an asset. Omitted fields are unchanged."},"Asset":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique asset identifier"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Client-supplied reference id provided at upload, if any"},"asset_type":{"$ref":"#/components/schemas/AssetType","description":"Asset role: reference (a file you uploaded), intermediate (produced mid-run), or output (a run's final result)"},"media_type":{"$ref":"#/components/schemas/MediaType","description":"Media category: image, video, audio, file, or design (an HTML artboard, downloadable as a zip or a rendered image)"},"media_extension":{"$ref":"#/components/schemas/MediaExtension","description":"File extension of the stored file: png, jpg, mp4, pdf, etc."},"visibility":{"$ref":"#/components/schemas/AssetVisibility","description":"Visibility level: private (your organization only), or public / shared (readable by any authenticated caller that has the asset ID)"},"bookmarked":{"type":"boolean","title":"Bookmarked","description":"Whether the asset is bookmarked","default":false},"archived":{"type":"boolean","title":"Archived","description":"Whether the asset is archived. Also true for a deleted asset, which `GET /assets/{asset_id}` still returns","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag names applied to this asset, in their stored casing, sorted alphabetically (case-insensitive)"},"downloaded":{"type":"boolean","title":"Downloaded","description":"Whether a download link has ever been requested for this asset, through the API or the Pletor web app","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the asset was last modified"},"url":{"type":"string","title":"Url","description":"Signed URL to the full asset file. It expires (at most one hour after the response), so re-fetch the asset instead of storing it. While a generated asset is still processing, this is the provider's own short-lived URL"},"large_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large Url","description":"Stable CDN URL for a preview: a WebP rendition fitting 700x700 for images, a PNG poster frame for videos. Null for audio and other files. While an asset is still processing it is the provider's short-lived URL for images and null for videos"},"flow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Flow Id","description":"ID of the flow that produced this asset. Null for uploaded assets."},"run_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id","description":"ID of the run that produced this asset. Null for uploaded assets."},"asset_metadata":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}]},"type":"object","title":"Asset Metadata","description":"Technical metadata: generation_model, aspect_ratio, duration, etc. Keys vary by media type and origin"},"is_flow_output_sample":{"type":"boolean","title":"Is Flow Output Sample","description":"Whether this asset is a sample output displayed on the flow card","default":false},"blur_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blur Hash","description":"BlurHash string for progressive image loading. Null until processing completes, and for audio and other files"},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Deprecated alias for `flow_id`, removed 31 December 2026.","deprecated":true,"readOnly":true}},"type":"object","required":["id","asset_type","media_type","media_extension","visibility","created_at","updated_at","url","agent_id"],"title":"Asset","description":"Complete asset with all metadata."},"AssetType":{"type":"string","enum":["reference","intermediate","output"],"title":"AssetType","description":"Classification of assets by their role in workflows."},"MediaType":{"type":"string","enum":["image","video","audio","file","design"],"title":"MediaType","description":"Primary media categories for assets."},"MediaExtension":{"type":"string","enum":["png","jpg","jpeg","svg","heic","heif","avif","webp","gif","mp4","wav","mp3","mov","webm","pdf","docx","xlsx","pptx","csv","txt","json","md","html","css","js","xml","yaml","yml","zip","ai","eps","ttf","otf","woff","woff2"],"title":"MediaExtension"},"AssetVisibility":{"type":"string","enum":["public","shared","private"],"title":"AssetVisibility"},"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}":{"patch":{"tags":["assets"],"summary":"Update Asset","description":"Update an asset's organization flags.\n\nUse this endpoint to bookmark/unbookmark and archive/unarchive an asset.\nFields omitted from the request body, or sent as `null`, are left unchanged;\nan empty body returns the asset as it is.\n\n**Request body:**\n- `bookmarked`: Optional boolean. `true` bookmarks the asset, `false`\n  removes the bookmark.\n- `archived`: Optional boolean. `true` archives the asset (it disappears\n  from default listings; retrieve it with `GET /assets?archived=true`),\n  `false` unarchives it.\n\n**Response:**\nReturns the complete updated `Asset` object.\n\n**Access Control:**\n- Asset must be owned by the authenticated user's organization. Assets from\n  other organizations are reported as `404` rather than updated.\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found or access denied\n- 409: `archived: true` was sent while the asset is still being processed —\n  retry once processing has completed (bookmarking is allowed meanwhile)\n- 410: Asset has been deleted\n- 422: Invalid request body (a field is not a boolean) or malformed `asset_id`\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_update_asset_assets__asset_id__patch","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Add Asset Tags

> Add tags to an asset.\
> \
> Tags are named labels scoped to your organization, shared with the Pletor\
> web app. Adding a tag name that doesn't exist yet creates it automatically;\
> names are matched case-insensitively, and tags already on the asset are\
> skipped, so this endpoint is safe to retry. Leading and trailing whitespace\
> is trimmed. A name matching an existing tag reuses it with that tag's\
> original casing: once \`Summer\` exists, adding \`summer\` attaches \`Summer\`.\
> \
> An asset that is still being processed (for example a fresh upload) can be\
> tagged straight away.\
> \
> \*\*Request body:\*\*\
> \- \`tags\`: Non-empty list of tag names to add (1-50 characters each, after\
> &#x20; trimming).\
> \
> \*\*Response:\*\*\
> Returns the complete updated \`Asset\` object; its \`tags\` field reflects all\
> tags now on the asset.\
> \
> \*\*Retrieval:\*\*\
> Filter assets by tag with \`GET /assets?tags=\<name>\` (repeatable, matches\
> assets carrying any of the given tags). An asset appears there once its\
> processing has completed.\
> \
> \*\*Access Control:\*\*\
> \- Asset must be owned by the authenticated user's organization.\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found or access denied\
> \- 410: Asset has been deleted\
> \- 422: Empty \`tags\` list, or a tag name that is blank or longer than 50\
> &#x20; characters\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"AssetAddTagsRequest":{"properties":{"tags":{"items":{"type":"string","maxLength":50,"minLength":1},"type":"array","minItems":1,"title":"Tags","description":"Tag names to add (at least one; 1-50 characters each after trimming surrounding whitespace; case-insensitive). Tags that don't exist yet in your organization are created automatically; tags already on the asset are skipped."}},"type":"object","required":["tags"],"title":"AssetAddTagsRequest","description":"Tag names to add to an asset."},"Asset":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique asset identifier"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Client-supplied reference id provided at upload, if any"},"asset_type":{"$ref":"#/components/schemas/AssetType","description":"Asset role: reference (a file you uploaded), intermediate (produced mid-run), or output (a run's final result)"},"media_type":{"$ref":"#/components/schemas/MediaType","description":"Media category: image, video, audio, file, or design (an HTML artboard, downloadable as a zip or a rendered image)"},"media_extension":{"$ref":"#/components/schemas/MediaExtension","description":"File extension of the stored file: png, jpg, mp4, pdf, etc."},"visibility":{"$ref":"#/components/schemas/AssetVisibility","description":"Visibility level: private (your organization only), or public / shared (readable by any authenticated caller that has the asset ID)"},"bookmarked":{"type":"boolean","title":"Bookmarked","description":"Whether the asset is bookmarked","default":false},"archived":{"type":"boolean","title":"Archived","description":"Whether the asset is archived. Also true for a deleted asset, which `GET /assets/{asset_id}` still returns","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag names applied to this asset, in their stored casing, sorted alphabetically (case-insensitive)"},"downloaded":{"type":"boolean","title":"Downloaded","description":"Whether a download link has ever been requested for this asset, through the API or the Pletor web app","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the asset was last modified"},"url":{"type":"string","title":"Url","description":"Signed URL to the full asset file. It expires (at most one hour after the response), so re-fetch the asset instead of storing it. While a generated asset is still processing, this is the provider's own short-lived URL"},"large_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large Url","description":"Stable CDN URL for a preview: a WebP rendition fitting 700x700 for images, a PNG poster frame for videos. Null for audio and other files. While an asset is still processing it is the provider's short-lived URL for images and null for videos"},"flow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Flow Id","description":"ID of the flow that produced this asset. Null for uploaded assets."},"run_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id","description":"ID of the run that produced this asset. Null for uploaded assets."},"asset_metadata":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}]},"type":"object","title":"Asset Metadata","description":"Technical metadata: generation_model, aspect_ratio, duration, etc. Keys vary by media type and origin"},"is_flow_output_sample":{"type":"boolean","title":"Is Flow Output Sample","description":"Whether this asset is a sample output displayed on the flow card","default":false},"blur_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blur Hash","description":"BlurHash string for progressive image loading. Null until processing completes, and for audio and other files"},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Deprecated alias for `flow_id`, removed 31 December 2026.","deprecated":true,"readOnly":true}},"type":"object","required":["id","asset_type","media_type","media_extension","visibility","created_at","updated_at","url","agent_id"],"title":"Asset","description":"Complete asset with all metadata."},"AssetType":{"type":"string","enum":["reference","intermediate","output"],"title":"AssetType","description":"Classification of assets by their role in workflows."},"MediaType":{"type":"string","enum":["image","video","audio","file","design"],"title":"MediaType","description":"Primary media categories for assets."},"MediaExtension":{"type":"string","enum":["png","jpg","jpeg","svg","heic","heif","avif","webp","gif","mp4","wav","mp3","mov","webm","pdf","docx","xlsx","pptx","csv","txt","json","md","html","css","js","xml","yaml","yml","zip","ai","eps","ttf","otf","woff","woff2"],"title":"MediaExtension"},"AssetVisibility":{"type":"string","enum":["public","shared","private"],"title":"AssetVisibility"},"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}/tags":{"post":{"tags":["assets"],"summary":"Add Asset Tags","description":"Add tags to an asset.\n\nTags are named labels scoped to your organization, shared with the Pletor\nweb app. Adding a tag name that doesn't exist yet creates it automatically;\nnames are matched case-insensitively, and tags already on the asset are\nskipped, so this endpoint is safe to retry. Leading and trailing whitespace\nis trimmed. A name matching an existing tag reuses it with that tag's\noriginal casing: once `Summer` exists, adding `summer` attaches `Summer`.\n\nAn asset that is still being processed (for example a fresh upload) can be\ntagged straight away.\n\n**Request body:**\n- `tags`: Non-empty list of tag names to add (1-50 characters each, after\n  trimming).\n\n**Response:**\nReturns the complete updated `Asset` object; its `tags` field reflects all\ntags now on the asset.\n\n**Retrieval:**\nFilter assets by tag with `GET /assets?tags=<name>` (repeatable, matches\nassets carrying any of the given tags). An asset appears there once its\nprocessing has completed.\n\n**Access Control:**\n- Asset must be owned by the authenticated user's organization.\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found or access denied\n- 410: Asset has been deleted\n- 422: Empty `tags` list, or a tag name that is blank or longer than 50\n  characters\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_add_asset_tags_assets__asset_id__tags_post","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetAddTagsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Remove Asset Tag

> Remove a tag from an asset.\
> \
> The tag is matched by name, case-insensitively. Only the link between the\
> asset and the tag is removed — the tag itself remains available in your\
> organization. Removing a tag the asset doesn't carry succeeds as a no-op,\
> so this endpoint is safe to retry.\
> \
> \*\*Parameters:\*\*\
> \- \`asset\_id\`: UUID of the asset\
> \- \`tag\_name\`: Name of the tag to remove (URL-encode names containing\
> &#x20; spaces or special characters)\
> \
> \*\*Response:\*\*\
> \- \`204 No Content\` on success\
> \
> \*\*Access Control:\*\*\
> \- Asset must be owned by the authenticated user's organization.\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found, access denied, or no tag with this name exists in\
> &#x20; your organization\
> \- 410: Asset has been deleted\
> \- 422: \`asset\_id\` is not a valid UUID\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}/tags/{tag_name}":{"delete":{"tags":["assets"],"summary":"Remove Asset Tag","description":"Remove a tag from an asset.\n\nThe tag is matched by name, case-insensitively. Only the link between the\nasset and the tag is removed — the tag itself remains available in your\norganization. Removing a tag the asset doesn't carry succeeds as a no-op,\nso this endpoint is safe to retry.\n\n**Parameters:**\n- `asset_id`: UUID of the asset\n- `tag_name`: Name of the tag to remove (URL-encode names containing\n  spaces or special characters)\n\n**Response:**\n- `204 No Content` on success\n\n**Access Control:**\n- Asset must be owned by the authenticated user's organization.\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found, access denied, or no tag with this name exists in\n  your organization\n- 410: Asset has been deleted\n- 422: `asset_id` is not a valid UUID\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_remove_asset_tag_assets__asset_id__tags__tag_name__delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"tag_name","in":"path","required":true,"schema":{"type":"string","description":"Name of the tag to remove, matched case-insensitively. URL-encode names containing spaces or special characters.","title":"Tag Name"},"description":"Name of the tag to remove, matched case-insensitively. URL-encode names containing spaces or special characters."},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Upload Asset

> Upload an asset file.\
> \
> This endpoint handles the complete asset upload process in a single request:\
> send your file and get back the created asset, with \`asset\_type: reference\`.\
> \
> \*\*Request (multipart/form-data):\*\*\
> \- \`file\`: The file to upload (required, up to 40 MB). Set the part's\
> &#x20; \`Content-Type\` to the file's real MIME type — the type is read from that\
> &#x20; header, not from the file's content. The file's name only picks between\
> &#x20; extensions that share a MIME type (\`.ai\` / \`.eps\`, \`.jpg\` / \`.jpeg\`).\
> \- \`visibility\`: Accepted for compatibility; uploads are currently always\
> &#x20; stored as \`"private"\` (only accessible by your organization).\
> \- \`filename\`: Optional display filename, used as the downloaded file's name.\
> &#x20; Defaults to the uploaded file's own name.\
> \- \`external\_id\`: Optional client-supplied reference id (≤255 chars), unique\
> &#x20; per organization. Makes the upload safe to retry — reusing a key returns\
> &#x20; \`409\` instead of a duplicate asset; fetch the existing one via\
> &#x20; \`GET /assets?external\_id=\`.\
> \
> \*\*Supported File Types:\*\*\
> \- Images: png, jpg/jpeg, webp, gif, svg, heic, heif, avif\
> \- Video: mp4, mov, webm\
> \- Audio: mp3, wav\
> \- Documents and other files: pdf, docx, xlsx, pptx, csv, txt, json, md,\
> &#x20; html, css, js, xml, yaml/yml, zip, ai, eps, ttf, otf, woff, woff2\
> \
> \*\*Response:\*\*\
> Returns the created \`Asset\` object (with \`external\_id\` echoed). Its \`id\` can\
> be passed to a run straight away — the run waits for processing to finish.\
> \
> \*\*Processing:\*\*\
> The asset is processed in the background after upload (resizing, previews,\
> metadata extraction). Until processing completes, its \`url\` / \`large\_url\`\
> may not resolve yet, \`GET /assets/{asset\_id}/download\` answers \`425\`, the\
> asset is left out of \`GET /assets\` listings (find it with\
> \`GET /assets/{asset\_id}\` or \`GET /assets?external\_id=\`), and it cannot be\
> archived. It can already be tagged, bookmarked or deleted.\
> \
> \*\*Errors:\*\*\
> \- 400: Missing or unsupported file type (see above), or file cannot be read\
> \- 401: Missing or invalid API key\
> \- 409: \`external\_id\` already used by another asset (don't retry; GET it instead)\
> \- 413: File larger than 40 MB. Rejected by the gateway before it reaches the\
> &#x20; API, so the body is not the usual JSON error envelope.\
> \- 422: Missing \`file\` part, or \`external\_id\` longer than 255 characters\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header\
> \- 500: Server error during upload or processing\
> \- 503: Storage service temporarily unavailable

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"Body_upload_asset_assets_upload__post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"The file to upload. Its type is taken from this part's `Content-Type` header, which must name a supported type; `application/octet-stream` or a missing type is rejected with 400."},"visibility":{"type":"string","title":"Visibility","description":"Requested visibility. Uploads are currently always stored as `private`, whatever this value is.","default":"private"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename","description":"Display filename, used as the name of the downloaded file. Defaults to the uploaded file's own name."},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id","description":"Optional client-supplied reference id, unique per organization. Reusing one returns 409; fetch the existing asset via GET /assets?external_id=. Makes the upload safe to retry without creating a duplicate."}},"type":"object","required":["file"],"title":"Body_upload_asset_assets_upload__post"},"Asset":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique asset identifier"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id","description":"Client-supplied reference id provided at upload, if any"},"asset_type":{"$ref":"#/components/schemas/AssetType","description":"Asset role: reference (a file you uploaded), intermediate (produced mid-run), or output (a run's final result)"},"media_type":{"$ref":"#/components/schemas/MediaType","description":"Media category: image, video, audio, file, or design (an HTML artboard, downloadable as a zip or a rendered image)"},"media_extension":{"$ref":"#/components/schemas/MediaExtension","description":"File extension of the stored file: png, jpg, mp4, pdf, etc."},"visibility":{"$ref":"#/components/schemas/AssetVisibility","description":"Visibility level: private (your organization only), or public / shared (readable by any authenticated caller that has the asset ID)"},"bookmarked":{"type":"boolean","title":"Bookmarked","description":"Whether the asset is bookmarked","default":false},"archived":{"type":"boolean","title":"Archived","description":"Whether the asset is archived. Also true for a deleted asset, which `GET /assets/{asset_id}` still returns","default":false},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Tag names applied to this asset, in their stored casing, sorted alphabetically (case-insensitive)"},"downloaded":{"type":"boolean","title":"Downloaded","description":"Whether a download link has ever been requested for this asset, through the API or the Pletor web app","default":false},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset was created"},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the asset was last modified"},"url":{"type":"string","title":"Url","description":"Signed URL to the full asset file. It expires (at most one hour after the response), so re-fetch the asset instead of storing it. While a generated asset is still processing, this is the provider's own short-lived URL"},"large_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Large Url","description":"Stable CDN URL for a preview: a WebP rendition fitting 700x700 for images, a PNG poster frame for videos. Null for audio and other files. While an asset is still processing it is the provider's short-lived URL for images and null for videos"},"flow_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Flow Id","description":"ID of the flow that produced this asset. Null for uploaded assets."},"run_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Run Id","description":"ID of the run that produced this asset. Null for uploaded assets."},"asset_metadata":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"type":"null"}]},"type":"object","title":"Asset Metadata","description":"Technical metadata: generation_model, aspect_ratio, duration, etc. Keys vary by media type and origin"},"is_flow_output_sample":{"type":"boolean","title":"Is Flow Output Sample","description":"Whether this asset is a sample output displayed on the flow card","default":false},"blur_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Blur Hash","description":"BlurHash string for progressive image loading. Null until processing completes, and for audio and other files"},"agent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Id","description":"Deprecated alias for `flow_id`, removed 31 December 2026.","deprecated":true,"readOnly":true}},"type":"object","required":["id","asset_type","media_type","media_extension","visibility","created_at","updated_at","url","agent_id"],"title":"Asset","description":"Complete asset with all metadata."},"AssetType":{"type":"string","enum":["reference","intermediate","output"],"title":"AssetType","description":"Classification of assets by their role in workflows."},"MediaType":{"type":"string","enum":["image","video","audio","file","design"],"title":"MediaType","description":"Primary media categories for assets."},"MediaExtension":{"type":"string","enum":["png","jpg","jpeg","svg","heic","heif","avif","webp","gif","mp4","wav","mp3","mov","webm","pdf","docx","xlsx","pptx","csv","txt","json","md","html","css","js","xml","yaml","yml","zip","ai","eps","ttf","otf","woff","woff2"],"title":"MediaExtension"},"AssetVisibility":{"type":"string","enum":["public","shared","private"],"title":"AssetVisibility"},"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/upload/":{"post":{"tags":["assets"],"summary":"Upload Asset","description":"Upload an asset file.\n\nThis endpoint handles the complete asset upload process in a single request:\nsend your file and get back the created asset, with `asset_type: reference`.\n\n**Request (multipart/form-data):**\n- `file`: The file to upload (required, up to 40 MB). Set the part's\n  `Content-Type` to the file's real MIME type — the type is read from that\n  header, not from the file's content. The file's name only picks between\n  extensions that share a MIME type (`.ai` / `.eps`, `.jpg` / `.jpeg`).\n- `visibility`: Accepted for compatibility; uploads are currently always\n  stored as `\"private\"` (only accessible by your organization).\n- `filename`: Optional display filename, used as the downloaded file's name.\n  Defaults to the uploaded file's own name.\n- `external_id`: Optional client-supplied reference id (≤255 chars), unique\n  per organization. Makes the upload safe to retry — reusing a key returns\n  `409` instead of a duplicate asset; fetch the existing one via\n  `GET /assets?external_id=`.\n\n**Supported File Types:**\n- Images: png, jpg/jpeg, webp, gif, svg, heic, heif, avif\n- Video: mp4, mov, webm\n- Audio: mp3, wav\n- Documents and other files: pdf, docx, xlsx, pptx, csv, txt, json, md,\n  html, css, js, xml, yaml/yml, zip, ai, eps, ttf, otf, woff, woff2\n\n**Response:**\nReturns the created `Asset` object (with `external_id` echoed). Its `id` can\nbe passed to a run straight away — the run waits for processing to finish.\n\n**Processing:**\nThe asset is processed in the background after upload (resizing, previews,\nmetadata extraction). Until processing completes, its `url` / `large_url`\nmay not resolve yet, `GET /assets/{asset_id}/download` answers `425`, the\nasset is left out of `GET /assets` listings (find it with\n`GET /assets/{asset_id}` or `GET /assets?external_id=`), and it cannot be\narchived. It can already be tagged, bookmarked or deleted.\n\n**Errors:**\n- 400: Missing or unsupported file type (see above), or file cannot be read\n- 401: Missing or invalid API key\n- 409: `external_id` already used by another asset (don't retry; GET it instead)\n- 413: File larger than 40 MB. Rejected by the gateway before it reaches the\n  API, so the body is not the usual JSON error envelope.\n- 422: Missing `file` part, or `external_id` longer than 255 characters\n- 429: Rate limit exceeded — retry after the `Retry-After` header\n- 500: Server error during upload or processing\n- 503: Storage service temporarily unavailable","operationId":"v1_upload_asset_assets_upload__post","parameters":[{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_asset_assets_upload__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Asset"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```

## Download Asset

> Get a download link for an asset's file.\
> \
> The response does not carry the file itself. It is a \`text/event-stream\`\
> body holding one event, \`data: \<url>\`, where \`\<url>\` is a signed link to the\
> file. Fetch that URL (no API key needed) to get the bytes; it is served as\
> an attachment named after the asset's filename and expires after one hour,\
> so request a fresh link rather than storing it. Each call marks the asset\
> as \`downloaded\`.\
> \
> \*\*Primary Use Cases:\*\*\
> \- Download generated images/videos from completed flow executions\
> \- Retrieve uploaded assets for local processing or display\
> \
> \*\*Parameters:\*\*\
> \- \`asset\_id\`: Asset UUID identifier\
> \- \`svg\`: Optional boolean. \`true\` converts a raster image to SVG and links\
> &#x20; to the converted file (experimental; SVG sources and non-image assets are\
> &#x20; returned unchanged)\
> \
> \*\*Access Control:\*\*\
> \- Same access rules as \`GET /assets/{asset\_id}\`: owned by your organization,\
> &#x20; or \`public\` / \`shared\`\
> \
> \*\*Errors:\*\*\
> \- 401: Missing or invalid API key\
> \- 404: Asset not found or access denied\
> \- 410: Asset has been deleted, or its file is no longer available\
> \- 422: Asset processing failed (re-upload the file), SVG conversion is not\
> &#x20; available for this asset, or malformed \`asset\_id\`\
> \- 425: Asset is still being processed — retry in a moment\
> \- 429: Rate limit exceeded — retry after the \`Retry-After\` header

```json
{"openapi":"3.1.0","info":{"title":"Pletor - Public API","version":"2.0.0"},"tags":[{"name":"assets","description":"Upload, retrieve, download, and organize files used as inputs or generated as outputs."}],"servers":[{"url":"/api/public"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito JWT Bearer token"}},"schemas":{"ApiErrorResponse":{"description":"Standard error response envelope. All API errors follow this format.","properties":{"error":{"$ref":"#/components/schemas/ApiErrorDetail","description":"Error details"}},"required":["error"],"title":"ApiErrorResponse","type":"object"},"ApiErrorDetail":{"description":"Structured details for an API error.","properties":{"code":{"$ref":"#/components/schemas/ApiErrorCode","description":"Machine-readable error code; branch on this, not on `message`"},"message":{"description":"Human-readable error description","title":"Message","type":"string"},"status":{"description":"HTTP status code of this response","title":"Status","type":"integer"},"details":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"boolean"},{"items":{"type":"string"},"type":"array"},{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}]},"description":"Additional context. Always holds `request_id` (the response's `X-Request-Id`); `validation_error` from request validation adds `errors`, a list of `location: message` strings, and `rate_limit_exceeded` adds `retry_after`, the seconds to wait.","title":"Details","type":"object"}},"required":["code","message","status","details"],"title":"ApiErrorDetail","type":"object"},"ApiErrorCode":{"description":"Machine-readable error code, with its usual HTTP status: `invalid_input`\n400, `authentication_required` 401, `forbidden` 403, `not_found` 404,\n`conflict` 409, `validation_error` 422, `rate_limit_exceeded` 429,\n`internal_error` 500, `run_failed` 502, `service_unavailable` 503, `timeout`\n504. A 4xx status without a code of its own is sent as `invalid_input`.","enum":["invalid_input","authentication_required","forbidden","not_found","conflict","validation_error","rate_limit_exceeded","internal_error","run_failed","service_unavailable","timeout"],"title":"ApiErrorCode","type":"string"}}},"paths":{"/v1/assets/{asset_id}/download":{"get":{"tags":["assets"],"summary":"Download Asset","description":"Get a download link for an asset's file.\n\nThe response does not carry the file itself. It is a `text/event-stream`\nbody holding one event, `data: <url>`, where `<url>` is a signed link to the\nfile. Fetch that URL (no API key needed) to get the bytes; it is served as\nan attachment named after the asset's filename and expires after one hour,\nso request a fresh link rather than storing it. Each call marks the asset\nas `downloaded`.\n\n**Primary Use Cases:**\n- Download generated images/videos from completed flow executions\n- Retrieve uploaded assets for local processing or display\n\n**Parameters:**\n- `asset_id`: Asset UUID identifier\n- `svg`: Optional boolean. `true` converts a raster image to SVG and links\n  to the converted file (experimental; SVG sources and non-image assets are\n  returned unchanged)\n\n**Access Control:**\n- Same access rules as `GET /assets/{asset_id}`: owned by your organization,\n  or `public` / `shared`\n\n**Errors:**\n- 401: Missing or invalid API key\n- 404: Asset not found or access denied\n- 410: Asset has been deleted, or its file is no longer available\n- 422: Asset processing failed (re-upload the file), SVG conversion is not\n  available for this asset, or malformed `asset_id`\n- 425: Asset is still being processed — retry in a moment\n- 429: Rate limit exceeded — retry after the `Retry-After` header","operationId":"v1_download_asset_assets__asset_id__download_get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","description":"ID of the asset","title":"Asset Id"},"description":"ID of the asset"},{"name":"svg","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true` vectorizes a raster image and returns a URL to the SVG version. Ignored for SVG sources and non-image assets, which are served as they are.","title":"Svg"},"description":"`true` vectorizes a raster image and returns a URL to the SVG version. Ignored for SVG sources and non-image assets, which are served as they are."},{"name":"X-Organization-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Active organization ID for multi-org support","title":"X-Organization-Id"},"description":"Active organization ID for multi-org support"}],"responses":{"200":{"description":"A `text/event-stream` body holding a single event whose data is a signed download URL, valid for one hour.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"422":{"description":"Validation error (`validation_error`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrorResponse"}}}}}}}}}
```
