The 180-day payload default applies to requests created on or after this policy took effect. Older requests are kept until you delete them — nothing was removed retroactively.
Generated media
Set an expiration on the files a request produces with theX-Modelrunner-Object-Lifecycle-Preference header:
The countdown starts when the request finishes, not when you submit it — so a 60-second expiration on a model that takes two minutes still gives you a full minute of file life after the output exists.
The same header works on file uploads, setting an expiration on the file you upload — send it on
POST /storage/upload/initiate. There the countdown starts at upload time, since the bytes are landing immediately.
One exception is worth knowing: an uploaded file that you then use as a request input stops expiring. Another request may reference the same upload, so we stop treating it as disposable. The same applies to a file you favorite, tag, or use as a model, wrapper or collection image. Delete those explicitly when you are done with them.
For a multipart upload, send the header on
POST /storage/upload/complete instead — that is the call that records the file.One client-side gap: the JavaScript SDK’s storage.upload() switches to multipart above 90 MB and completes that upload without going through the API, so a lifecycle option is not applied to files that large. Use the raw HTTP flow, or delete large uploads explicitly.Access controls are not supported
CDN URLs are unguessable but public to anyone holding the link. There is no per-user access-control list, and aninitial_acl field in the lifecycle header is rejected with a 400 rather than ignored — a silently-dropped ACL would leave you believing files are private when they are readable. Treat the URL itself as the secret, and use a short expiration for sensitive output.
Request payloads
Inputs and outputs are stored for 180 days, which is what makes your dashboard history work. To keep them out of storage entirely, sendX-Modelrunner-Store-IO: 0:
This is not “never written”. We hold the output briefly while we move media onto the CDN, build thumbnails and calculate what to charge you — usage-priced models are billed from the output itself. Payloads are removed shortly after the request settles, once your result has been handed over and any webhook body has been rendered. Budget for a window of roughly 15 minutes, not zero.Your media is unaffected: files stay for as long as the expiration above allows.
Deleting a request’s data
/payloads to the response_url the submit returned.
This removes the input and output JSON and deletes the media files that request generated:
X-Idempotency-Key is optional; repeating the same delete is safe either way.
A delete returns 409 in three cases, each protecting something:
- The request has not finished. Its payload does not exist yet in final form.
- It has not been charged yet. Usage-priced models compute the bill from the output, so removing it first would produce a wrong charge.
- It is published as an example on a model or wrapper page. Detach it first.
Reading a request whose payloads are gone
The request record survives a purge — only the payloads are emptied.input and output come back as {}, and a payloadsPurgedAt timestamp tells you why.
Key your handling off
payloadsPurgedAt, not off an empty output — {} is also what a failed generation looks like. See the billingStatus warning on the webhooks page for the same distinction.Setting a default for your account
Rather than sending headers on every call, set a default under Settings → Account in the dashboard: payload retention, a media expiration, and whether to store payloads at all. It applies to every request on the account. A header on an individual request always wins over the account default — including"expiration_duration_seconds": null, which is how you exempt one request from an account-wide media expiration.
