Amba
SDKsFeatures

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

import { Amba } from '@layers/amba-web';
 
async function onFileChange(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (!file) return;
 
  const asset = await Amba.storage.upload({
    bucket: 'avatars',
    file,
    retentionDays: 30,
  });
 
  console.log(asset.url); // CDN URL
}

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.

const asset = await Amba.storage.upload({
  bucket: 'avatars',
  file, // Browser File / Blob, or RN Blob, or platform native data
  filename: 'me.jpg', // optional — defaults to file.name where available
  retentionDays: 30, // optional — defaults to the bucket's policy
});
 
// Returned MediaAsset:
//   {
//     id: 'asset_...',
//     bucket: 'avatars',
//     key: 'user/.../me.jpg',
//     url: 'https://{projectId}.cdn.amba.host/avatars/...',
//     mime_type: 'image/jpeg',
//     size_bytes: 123456,
//     created_at: '2026-05-13T...',
//   }

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.

import { AmbaClient } from '@layers/amba-node';
 
const amba = await AmbaClient.configure({
  projectId: process.env.AMBA_PROJECT_ID!,
  clientKey: process.env.AMBA_CLIENT_KEY!,
});
 
// 1. Get a signed PUT URL + an upload id
const presign = await amba.storage.presign({
  bucket: 'invoices',
  filename: `invoice-${id}.pdf`,
  mimeType: 'application/pdf',
  sizeBytes: pdfBuffer.byteLength,
});
 
// 2. PUT the bytes to the signed URL (any HTTP client; the signed
//    URL embeds the auth)
await fetch(presign.upload_url, {
  method: 'PUT',
  headers: Object.fromEntries(presign.upload_headers),
  body: pdfBuffer,
});
 
// 3. Tell the server the upload is done — the server checks the file
//    arrived and returns the final asset metadata
const asset = await amba.storage.commit(presign.upload_id, presign.asset_id);

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.

  • commit checks that the file arrived with a non-zero size. When nothing arrived it answers 409 UPLOAD_NOT_FOUND and the asset stays pending: PUT the 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, commit answers 503 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 sizeBytes holds 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 with storage.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.

// Client uploads
const asset = await Amba.storage.upload({ bucket: 'avatars', file });
 
// Save the asset id (not the URL) against your user profile
await Amba.collections.update('users', userId, { avatar_asset_id: asset.id });

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), and pending (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-key PUT presign answer 404 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 media bucket. Other buckets carry their own cap, configurable via the admin API.
  • Supported MIME types: each bucket has an allow-list. The default media bucket 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). A mimeType outside the list is rejected with a 400. 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/buckets with { "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 referencing asset_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 include bucket_path (the URL path segment assets are served under; defaults to the bucket name). The legacy r2_bucket_path field is a deprecated alias of bucket_path — still accepted on create and emitted in responses for back-compat, but new integrations should use bucket_path.
  • Auth feature — prerequisite.
  • Per-platform quickstarts: Web, Node, iOS, Android, Flutter, Unity.

On this page