If you are building an uncensored AI image or video product, three facts decide your storage design. Uploaded reference files last 1 day. Generated media from API, SDK and Playground tasks lasts about 14 days (Studio short-film media has a separate cap of about 60 days), and every download link you are handed works for only about 20 minutes. All of these are caps: you can only shorten them, either for the whole account or for one request, and you can destroy a finished task's content on demand. Nothing you set makes them longer.
So the dependable pattern is: upload directly with a short-lived ticket, commit the file, submit an asynchronous task with the private spicy:// reference, collect the result link when the task finishes, and either copy the result to storage you control or let it expire on purpose. Do not put a customer's upload in a public bucket, and do not save a returned download URL as if it were permanent.
Uploading a reference to SpicyAPI is free; the generation task that uses it is charged separately. Refreshing an existing result's download link does not run or buy the generation again. Creating a new generation attempt has its own model charge, while the storage, requests and transfer for a copy in your own bucket follow your storage provider's pricing.
Read the accepted retention deadlines before promising a storage window to customers; the retention documentation explains the settings. A practical local-copy example is in turning temporary AI media into durable product assets.
Try it in the Playground first
- Open the z-image text-to-image page, stay on Workbench in Form, type a Prompt, choose an Aspect ratio and select Run model (sign in first; this one image is a normal paid generation). Check it under Preview and save it with Download.
- In the Console, open Logs and find that task to see how long its output is kept.
- Open Data protection, then Retention, to see the four clocks and shorten any of them.
- Once you have your own copy, select Destroy outputs on the finished task to delete its content.
What SpicyAPI keeps, and for how long
| Data | Kept for | What happens at expiry |
|---|---|---|
| Reference files you upload | 1 day after a successful commit | The spicy:// URI stops working for new tasks (including retries); the object is then deleted, with cleanup lagging by up to 24 hours |
| Generated image, video and audio | About 14 days for API, SDK and Playground results; about 60 days inside a Spicy Studio short-film project, with up to 24 hours of cleanup lag | Deleted from object storage; no download link can be issued |
Prompt text and result file paths on the task (input, output) | 30 days | Both fields are cleared; the task row stays |
| Task record (ID, model, state, amount, timestamps) | Indefinitely | Kept as billing evidence; content fields are cleared on their own schedule |
| Billing ledger | As long as US tax and accounting law requires (a period measured in years) | Kept; unaffected by deleting content |
| Task logs and webhook delivery records | At least 60 days | Deleted |
| Database backups | 35 days | Roll over; deleting content does not erase backups instantly |
Shorten it, or destroy it, yourself
1. Account retention clocks. Under Retention on the console's Data protection page there are four separate clocks:
| Clock | Covers | Default | Shorter options |
|---|---|---|---|
| Generated results | Images, video, audio and the result payload | About 14 days | 1 hour, 6 hours, 24 hours, 7 days |
| Short-film media | Studio short-film media and exported films | About 60 days | 1 hour up to 30 days |
| Prompt text | The text fields of the request | 30 days | Same as results, 1 hour, 24 hours, 7 days |
| Uploaded files | Reference files | 1 day | Delete when the job ends |
The uploaded-files clock has three important limits:
- Delete when the job ends: an upload is removed once every task using it reaches a final state, normally within a minute. A new generation using that deleted reference needs another upload.
- Existing files only: that switch applies to files present when you save. Later uploads currently keep the standard 1-day expiry shown in their commit response.
- 1-hour and 24-hour choices: these currently change nothing; the upload remains usable until its returned
expiresAt.
Warning before you shorten anything: a shorter setting can reach back to tasks you already ran. Setting Prompt text to 1 or 24 hours, for example, clears the prompt text and the generated results of every task finished more than a day ago, even if Generated results is still 14 days. Deleted content cannot be restored. Download what you want to keep first; lengthening a setting only affects the future.
2. One request at a time. Send the X-Spicy-Retention header (seconds) when creating a task; the retention documentation shows the request. The effective window is the shortest of that request, your account setting and the platform cap. Read the accepted deadlines from the task rather than promising the requested duration.
Do not use immediate deletion for a product that promises delivery or a saved library. It can remove the result before your copy finishes. Leave a recovery margin after delivery.
3. Destroy a finished task. Once you have copied what you need, or when a customer requests deletion, remove that task's creative content.
The console offers the same action as Destroy outputs on a task in Logs. Three rules apply:
- Finished tasks only. Purge is idempotent by
taskId; it needs noIdempotency-Key. It neither cancels generation nor refunds the charge, andbillingRetainedremains true. - Deletion is scheduled.
contentStatebecomespurgedwhilemediaDeletionPendingstays true until the sweep finishes, normally within a minute. Confirm completion before telling a customer the bytes are gone. - A
presentresponse removed nothing. Investigate the response and preservation settings rather than reporting deletion completed.
contentState is expired when the retention clock removed the content and purged when you destroyed it. The task state expired is something else entirely: the generation timed out and produced nothing. Retrying helps only in that second case.
Send reference files
A reference image, video or audio file can reach a task three ways. A public HTTPS URL must return the file itself: a link to a share page (cloud drive, photo album, social post) returns a web page and the task fails with invalid_asset, so download the file and upload it instead. A small image (up to 1 MiB) can go inline as a base64 data URI. Anything private or large should be uploaded through the SDK, CLI or Playground, which returns a private spicy:// reference for the task.
Upload limits: JPEG, PNG, WebP and GIF up to 10 MiB; WAV, MP3, WebM, MP4 and MOV up to 90 MiB and 600 seconds, and a model can accept less. Keep an original copy if you will need the input again after its one-day window. Your backend only needs to authorize the upload and record ownership; the client can send the bytes directly. Uploading is free; generation is a separate purchase. The media documentation covers the flow.
Results: collect, then copy or let go

A manuscript and landscape frames converge on a single projected scene.
Submit each new request with its own idempotency key, keep the task ID, and collect the result by polling or callback. Ready assets carry a signed url; fetch it without an API key and send a User-Agent such as Mozilla/5.0, because the CDN rejects Python's default urllib agent with a 403 that does not mean the link expired. A succeeded task can still hold a pending asset, so keep checking until the URL is ready. An expired link can be refreshed within retention; the media documentation shows how.
Three states of an asset are worth handling explicitly:
| What you see | Meaning | What to do |
|---|---|---|
pending: true, no url yet | The file is still being copied into storage | Query again in a few seconds |
unavailable: true, no key | No retrievable platform asset: initial storage failed, retention ended or purge removed it | Recover a verified copy you saved; if none exists, explicitly decide on a new generation |
HTTP 410 from download-url | The platform copy is past retention or was destroyed | The platform copy cannot be restored; use your own archive if available |
A text-only model has no assets at all, and its text in output.text is erased on the same schedule, so read it before assuming a file exists.
The no-charge storage exception applies when none of a task’s outputs could initially be stored. An old unavailable asset alone does not establish a refund; check the original task’s settlement.
Callbacks carry your prompt and signed result links. Your endpoint, reverse proxy, error tracker and log pipeline then receive material that SpicyAPI itself erases on a schedule, so redact callback bodies from access logs and do the copying in a worker. Signature checking, deduplication and retries are in the webhook documentation.
- Your product promises a library, sharing, or later editing: stream the result to private storage you control as soon as you see success, record your own asset ID, and (if you want the platform copy gone) call purge afterwards. Stream rather than buffer a large video, and make the destination write idempotent on task plus asset.
- One-time delivery: do not copy. Show the result, tell the customer how long it lasts, and let the retention clock, or a short
X-Spicy-Retention, remove it.
A user-uploaded reference is not a marketing asset, and a successful result is not consent to publish it. Only mark something "saved by customer" after your own copy is confirmed.
When something fails
| Boundary | Symptom | Right recovery |
|---|---|---|
| Create task | Response lost | Replay the saved body with the same idempotency key within its 24-hour deduplication window; stop for reconciliation when that window may have elapsed. A new key can create a second task and charge |
| Callback | Duplicate, late, or missed | Deduplicate by request_id; poll recordInfo when an expected terminal event never arrives |
| Result copy | Link expired before a queued fetch or reconnection | Fetch a fresh URL and stream again to the same destination key |
| Past the deadline | 410, or unavailable | Check your verified archive first; mark unavailable if no saved copy remains |
Keep logs useful without keeping creative material
Check these routes for accidental copies:
- Error reports and access logs: redact prompts, tickets and signed links; avoid logging media-route bodies.
- Analytics and queues: use opaque operation, task and asset IDs rather than URLs or filenames, including in dead-letter storage.
- Support exports and public delivery: check screenshots, page metadata and emails. Send customers to an authenticated page that issues a fresh link after checking access.
- Your storage and backups: apply your own deletion policy. An expired link cannot revoke a file someone already downloaded.
What retention settings cannot promise
- The model sees the prompt. The model that runs a request receives it in plaintext, so SpicyAPI's settings control only how long SpicyAPI itself keeps something. Do not send material you do not need to send.
- Purge is an asynchronous deletion request. Confirm its returned content state and completion of media deletion. Storage lifecycle cleanup and backup rollover have separate clocks; your own copied files and backups also need their own deletion operation. Billing records stay either way.
If you need a different retention period written into a contract, raise it before signing rather than inferring it from a documentation page.
FAQ
Can I use a temporary upload URL as an image URL in a generation request?
No. The upload URL only authorizes the direct write. Upload, commit, and put the committed spicy:// URI in a field the live model schema marks as an upload field.
Can I keep a generated video on SpicyAPI longer than 14 days?
For API, SDK and Playground results, no: their cap is about 14 days. Studio short-film media has a separate cap of about 60 days. Copy the file to your own storage after success for longer access.
Does purge refund the task?
No. Purge removes content only; the charge and task record stay.
Can a webhook replace polling completely?
It can be the main path, with a bounded reconciliation check for missed deliveries. Key your handler on the delivery request_id and the task ID.
Can I put a result link in an email?
Avoid it. Emails are forwarded and archived long after a short-lived link expires. Send customers to an authenticated page that checks access and issues a fresh link.
What should happen when a customer deletes a saved result?
Delete or revoke your own copy and its delivery path, and keep a record that you did. The platform's copy follows its own schedule; use Destroy outputs if it should go sooner.
Check an existing task from code
Install spicyapi, set SPICY_API_KEY and TASK_ID, then run this read-only check. It does not create a generation or delete content.
import json
import os
from spicyapi import SpicyClient
client = SpicyClient()
task = client.get_task(os.environ["TASK_ID"])
print(json.dumps({"taskId": task["taskId"], "state": task["state"],
"contentState": task.get("contentState"), "retention": task.get("retention"),
"mediaDeletionPending": task.get("mediaDeletionPending")}, indent=2))The retention reference covers the request settings and deletion contract.
Let an Agent handle the call
Delegate a storage check when you already have a task ID; no new generation is needed.
- CLI:
npx @spicyapi/cli tasks get "$TASK_ID"reads the current deadlines. See the CLI guide. - MCP: ask
spicyapi_task_getto inspect that task. Usespicyapi_task_purgeonly after explicitly approving destruction of its content; the MCP guide covers confirmation. - Skill: after
npx skills add spicyapi-ai/spicy-skill, ask$spicyapito inspect the task's retention and explain what will expire. See the Skill guide.





