Storage
Upload files from any SDK. Per-bucket retention, signed URLs, cascade-on-user-delete.
Upload images, documents, and other assets from any SDK with one call. Each file is stored against the signed-in user and surfaced via a CDN URL. Buckets are configured server-side with retention policies; assets are automatically removed when the owning user is deleted.
The client SDKs expose two shapes over the same three-step flow (register → PUT the bytes → finalize): a one-call upload({ bucket, file }) that runs all three for you, and an explicit presign + commit pair for when you want to control the PUT yourself (upload through your own server, apply pre-upload validation, or run the transfer in the background).
Quick start
Operations
One-call upload (upload)
Available on platforms where the SDK can directly hold the file body (Web, React Native, Swift). Encapsulates the full presign → PUT → commit flow. Use this when the customer is uploading directly from their device.
Two-phase: presign + commit (server-side only)
Use when the upload must flow through your own server (validation, virus scanning, content rewriting), or when you want to control the PUT yourself. Available on every SDK, including web. upload() is the same three steps run for you in one call.
When an upload counts
An asset is part of your project once its file has arrived. Registering an
upload (presign, the first step of upload) creates the asset as pending.
It becomes committed when its PUT finishes, and commit confirms it. Until
then the asset is left out of list, download URLs and media catalogs, get
answers 404 (a get that finds the file commits the asset), and it adds
nothing to media usage or storage overage.
commitchecks that the file arrived with a non-zero size. When nothing arrived it answers409 UPLOAD_NOT_FOUNDand the asset stays pending:PUTthe file to its upload URL, then commit again. An upload URL lasts about 10 minutes; once it has expired, register the upload again. When the check cannot run right now,commitanswers503 UPLOAD_UNVERIFIED; retry it.- An upload whose file never arrives is deleted by the daily cleanup once it is 24 hours old, so within about two days of registration.
- On a free project, an upload's
sizeBytesholds part of the 250 MB media budget from registration until the upload is committed (it then counts as stored), deleted, or deleted by the daily cleanup. Uploads started together cannot pass the budget between them, and a file that arrived without its commit being recorded stays inside the budget. To free the space of an upload you gave up on, delete it withstorage.delete.
Patterns
Per-user avatars
Buckets aren't user-scoped on the server, but every asset's user_id is stamped from the session. The CDN URL is public; if you want privacy-by-design, point clients at the asset id and gate URL issuance through a server function.
Retention
Pass retentionDays at upload to override the bucket's default
(retention_days on the bucket policy). A daily cleanup at 03:00 UTC deletes
each asset whose retention deadline has passed: its stored file first, then its
record. An expired file's URL stops serving within about a day of its deadline,
plus the CDN cache lifetime. Until that run, the asset stays readable.
Deleting an asset
storage.delete(assetId) (DELETE /v1/client/media/{assetId}) deletes one of
the signed-in user's assets. The stored file is deleted before the call
answers, so the URL stops serving once cached copies expire (about 5 minutes at
the edge for original files). When the file cannot be deleted, the call answers
503 STORAGE_DELETE_FAILED and the asset stays as it was; retry the delete.
From the delete on, the asset is gone from every read and stops counting toward
media usage. Its hidden record is removed by the daily cleanup 7 to 8 days
after the delete.
Assets deleted with storage.delete before October 4, 2026 kept their stored
file after the delete. The daily cleanup deletes those files and their records
too, so each of those URLs stops serving within about 7 days of the cleanup's
first run.
Cascade on user delete
Deleting a user deletes every asset they uploaded, in every bucket. Each user
delete does this: DELETE /v1/admin/projects/{projectId}/users/{userId}, the
?anonymous_id= form, DELETE …/users/{userId}/reset?delete=true, and the
sandbox reset, along with the amba_users_delete, amba_users_reset_user
(delete: true) and amba_users_reset_sandbox MCP tools that call them. No
additional work is needed in your app.
- At the delete. In the same transaction as the user delete, the user's
assets leave every read (the asset list,
get, media catalogs and the admin media list) and stop counting toward media usage. Catalog entries that point at them are removed. - Right after. Each asset's stored file is deleted, then its record. The
delete response reports the purge as
media_purge:assets(the user's assets),purged(files and records gone), andpending(left for the background retry). - Background retry. A file that cannot be deleted at once is retried every few minutes, backing off (up to six hours between tries) while storage keeps failing, until it is gone. An asset registered in the six hours before the delete has its file deleted at once and again six hours after it was registered, which removes the file an upload still streaming at the delete writes when it finishes within that time; its record stays hidden until then. The user delete itself always completes.
- No new uploads. A deleted user's session can no longer get an upload
URL:
upload,presign, and a raw-keyPUTpresign answer404 USER_NOT_FOUND.
Assets your project uploaded through the admin API, and other users' assets, stay. Copies a CDN edge or a browser already cached expire with their cache lifetime: about 5 minutes at the edge for original files, up to an hour for resized images (see Updates and cache freshness).
The purge covers registered assets: everything the SDK's upload, presign
and commit create, and every raw-key upload URL
(POST /v1/client/media/presign with method: "PUT"), which registers its
object as the user's asset. An object a function writes has no asset record,
so delete those yourself, for example from a user.deleted webhook.
Limits
- Max upload size: 50 MB for the default
mediabucket. Other buckets carry their own cap, configurable via the admin API. - Supported MIME types: each bucket has an allow-list. The default
mediabucket allows common image, video, and audio types (image/jpeg,image/png,image/webp,image/gif,video/mp4,video/webm,video/quicktime,video/x-m4v,audio/mpeg,audio/aac,audio/ogg,audio/mp4). AmimeTypeoutside the list is rejected with a400. The server records what you declare; it doesn't transcode or validate content. - CDN edge cache: asset responses cache at the CDN for ~5 minutes by default.
- Buckets are pre-created: defined via the admin API (
POST /v1/admin/projects/:projectId/storage/bucketswith{ "name": "avatars", "public": true }) before the client can upload. - Per-asset metadata: only the SDK-supplied fields (
bucket,filename,mimeType,sizeBytes,retentionDays) are first-class. For custom metadata, attach it to a collection row referencingasset_id.
Bucket policy management
Create and inspect policies through
/v1/admin/projects/{projectId}/storage/buckets. Mutable fields are
public, retention_days, max_size_bytes, and allowed_mime_types.
bucket_path is fixed at creation because it is part of every stored object's
URL and key layout.
PATCH replaces each field you send. In particular,
allowed_mime_types is a full replacement rather than an additive merge. To
add a type, read the current bucket first, append locally, and send the complete
array back; send null to make fronted uploads unrestricted by MIME type.
The size and MIME settings govern Amba-fronted upload URLs. A deployed
function's env.STORAGE is the project's trusted server-side object API and
does not represent one logical bucket policy. Function code is responsible for
choosing the intended bucket path and validating untrusted upload input before
writing it. Objects written directly by a function are not registered media
assets, so per-asset retention and delete cascades do not apply automatically.
Reference
- Client API — media — presign + commit endpoints.
- Bucket creation + policy management:
POST/GET/PATCH/DELETE /v1/admin/projects/:projectId/storage/buckets[/:name]. Bucket responses includebucket_path(the URL path segment assets are served under; defaults to the bucket name). The legacyr2_bucket_pathfield is a deprecated alias ofbucket_path— still accepted on create and emitted in responses for back-compat, but new integrations should usebucket_path. - Auth feature — prerequisite.
- Per-platform quickstarts: Web, Node, iOS, Android, Flutter, Unity.