---
title: "Save a file to your asset library"
method: POST
path: "/api/v1/assets"
tags: ["Assets"]
---

# Save a file to your asset library

`POST /api/v1/assets`

## Request

- Base URL: `https://api.popnow.ai`
- URL: `https://api.popnow.ai/api/v1/assets`
- Auth: HTTP bearer

Copies a file into the key's library (your personal library, or the
team's shared library for a team key), where it is kept until it is
deleted, the owner closes the account or the team is deleted. Choose one
source:

- `source: task_output` with `taskId` and either the output `key` or its
  zero-based `index` (the order of `output.assets` in `recordInfo`). The
  task must be one of your own successful tasks; with a team key, a task
  of that team.
- `source: upload` with the `fileId` of a committed upload.
- `source: url` with a public `http` or `https` address on port 80 or 443,
  without a username or password. The whole file is downloaded once
  through a fetcher that refuses private and internal addresses, and its
  type is read from the file itself: only JPEG, PNG, WebP and GIF images,
  MP4 and WebM video, and MP3, WAV, FLAC and Ogg audio are accepted.
  Links to PopNow storage are refused; save those with `task_output` or
  `upload` instead.

`Idempotency-Key` is required and bound to the request for 24 hours.
Saving the same source again returns the existing asset without using
more space. Dimensions and durations are measured by the server.

Storage unlocks with the owner's lifetime top-ups (see
`GET /api/v1/assets/usage`); the owner's personal library and all of
their team libraries share one allowance. Without any top-up, saving
returns 400 (`data.reason` `asset_library_locked`) explaining how much to
top up. A full allowance (`asset_library_full`) or a file above the
plan's per-file limit (`asset_file_too_large`) also returns 400 naming the
plan and what the next plan unlocks; for team members who cannot top up,
the message asks the team owner to upgrade instead of naming an amount.
Lowering a plan never deletes saved assets. Saving is limited to 30
requests per minute and a daily number of new assets per allowance (429
with `Retry-After` until 00:00 UTC, `asset_daily_limit`). If storage is
full across the platform the request returns 503 (`asset_storage_full`).
A team role that cannot save gets 403 (`asset_permission_denied`).

## Headers

- `Idempotency-Key` string, required

## Request body

- AssetCreateRequest
  - `source` 'task_output' | 'upload' | 'url', required
  - `taskId` string: source=task_output: one of your own successful tasks.
  - `key` string: source=task_output: the output key. Send key or index.
  - `index` integer: source=task_output: zero-based position in output.assets. Send key or index.
  - `fileId` string: source=upload: a committed upload (fil_… or popnow://f/fil_…).
  - `url` string: source=url: a public http or https address on port 80 or 443, without credentials.
  - `name` string: Defaults to "Saved asset".

## Response `200`

The saved (or already saved) asset.

- AssetEnvelope
  - `code` 200, required
  - `msg` 'success', required
  - `request_id` string, required: Correlation identifier. Include it in support requests.
  - `data` Asset, required
    - `id` string, required
    - `uri` string, required: Stable reference. Works only in generation requests made with keys of the same workspace; it is never a downloadable link.
    - `name` string, required
    - `kind` 'image' | 'video' | 'audio', required
    - `contentType` string, required
    - `bytes` integer, required
    - `width` integer
    - `height` integer
    - `durationSeconds` number
    - `source` 'task_output' | 'upload' | 'url', required
    - `sourceId` string, required: Your task or upload id; for url, an opaque digest of the address (the address itself is not stored).
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `previewUrl` string, required: Private preview signed for you for at most 20 minutes; empty when signing fails. Do not store or share it — persist uri instead.
    - `previewUrlExpiresAt` string, date-time
    - `favorite` boolean, required: Favorite mark of the library (shared by everyone in a team).
    - `model` string, required: Public name of the model that generated the asset, for example bytedance/seedream-5.0-pro/text-to-image; empty for uploads and links.
    - `modelName` string, required: Display name of that model; empty when the asset was not generated or the name is unknown.

## Other responses

- `400`: Invalid JSON, query, callback URL, model input, or media declaration. Invalid callBackUrl values return code 400 and msg "Invalid callback URL" with request_id. Submitted hosts, IP addresses, and DNS diagnostics are not echoed.
- `401`: Missing, malformed, expired, revoked, or otherwise invalid API key.
- `403`: Account, model, API-key, IP, or region authorization failed.
- `404`: Resource does not exist or is not visible to this account and API key. These cases are intentionally indistinguishable.
- `409`: Idempotency or media-transfer state conflicts with this request.
- `429`: Account-level request limit exceeded.
- `500`: Internal failure. Details are not exposed; report `request_id` to support.
- `503`: HTTP 503 carries one of three business codes. `50301`: no usable deployment or effective customer price exists for the selected model. `503`: a dependency is temporarily unavailable; retry after the `Retry-After` header when it is present. `50302`: a synchronous generation failed upstream and the charge was refunded. Nothing was charged, but send the retry with a **new** `Idempotency-Key`: reusing the original one replays the recorded failure instead of generating again.

## Changes

- **2026-10-20** `fede911a6e9b`: 96 warning
  - added the new `agent_project_budget` enum value to the `allOf[subschema #2]/data/reason` response property for the response status `400`
  - added the new `agent_project_budget` enum value to the `allOf[subschema #2]/data/reason` response property for the response status `401`
  - added the new `agent_project_budget` enum value to the `allOf[subschema #2]/data/reason` response property for the response status `403`
  - added the new `agent_project_budget` enum value to the `allOf[subschema #2]/data/reason` response property for the response status `404`
  - …92 more

[Operation history](https://skmtc.dev/popnow/apis/popnow-api/changes/api/v1/assets/post.md)

---

[API](https://skmtc.dev/popnow/apis/popnow-api.md) · [All operations](https://skmtc.dev/popnow/apis/popnow-api/llms.txt) · [OpenAPI document](https://skmtc.dev/popnow/apis/popnow-api/revisions/fede911a6e9b?raw)
