Amba
Platform

Media Management

Upload and manage media assets with signed URLs, folders, and metadata.

Upload images, videos, and other media assets. Amba handles signed upload URLs, folder organization, and metadata.

How it works

  1. Request an upload URL with filename and MIME type
  2. Upload the file to the returned signed URL
  3. The asset is registered with metadata, folder, and alt text
  4. Use the CDN URL in content items, user avatars, etc.

How the CDN serves those URLs — edge caching, image transforms, and byte-range requests for audio/video seek — is covered in Media Serving & CDN.

MCP tools

ToolDescription
amba_media_uploadRegister a media asset and get a signed upload URL
amba_media_listList media assets with pagination and folder filtering

Example

Agent: "Upload a hero banner image"

amba_media_upload({
  project_id: "proj_xxx",
  filename: "hero-banner.png",
  mime_type: "image/png",
  size_bytes: 245000,
  alt_text: "App hero banner showing workout progress"
})

Returns:

{
  "data": {
    "id": "media_xxx",
    "storage_url": "https://{projectId}.cdn.amba.host/media/hero-banner.png"
  },
  "upload_url": "https://{projectId}.cdn.amba.host/media/hero-banner.png?upload=true",
  "upload_url_expires_at": "2026-05-28T12:05:00Z"
}

upload_url and upload_url_expires_at are at the top level of the response — siblings of data, not nested inside it. data holds the asset row (id, storage_url, …); the upload URL sits next to it. Read response.upload_url, not response.data.upload_url (a common mistake that yields undefined and an upload that silently never happens).

Uploading the file

The upload_url is a short-lived, pre-authorized endpoint — PUT the raw file bytes to it:

  • Use PUT, with the file body as the request body.
  • Set Content-Type to exactly the mime_type you registered in the create call. A mismatch is rejected.
  • Do not send an Authorization header on the upload PUT — the upload_url is already authorized.
  • A 200 or 204 means the upload succeeded. After that, the file is served from the storage_url.
  • upload_url expires (see upload_url_expires_at). If it has expired, request a new one by registering the asset again.

The maximum file size for the media bucket is 50 MB.

Allowed MIME types

The default media bucket accepts these types. Registering an asset with a mime_type outside the list returns 400 MIME_NOT_ALLOWED.

CategoryAllowed types
Imageimage/jpeg, image/png, image/webp, image/gif
Videovideo/mp4, video/webm, video/quicktime, video/x-m4v
Audioaudio/mpeg, audio/aac, audio/ogg, audio/mp4

End-to-end example

A complete register → upload → fetch sequence:

# 1. Register the asset (returns upload_url + storage_url)
RESP=$(curl -s -X POST '${BASE_URL}/v1/admin/projects/{projectId}/media' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{"filename":"hero-banner.png","mime_type":"image/png"}')
 
# 2. Extract the upload URL and the final storage URL
UPLOAD_URL=$(echo "$RESP" | jq -r '.upload_url')
STORAGE_URL=$(echo "$RESP" | jq -r '.data.storage_url')
 
# 3. PUT the file bytes — Content-Type MUST match the registered mime_type,
#    and no Authorization header is needed on this request
curl -X PUT "$UPLOAD_URL" \
  -H 'Content-Type: image/png' \
  --data-binary @hero-banner.png
 
# 4. Fetch the asset from its public storage URL
curl -L "$STORAGE_URL" -o downloaded.png

Catalogs

A catalog is a named, curated bundle of media assets your app fetches in one call by slug — instead of hard-coding a list of asset URLs in client code and keeping it in sync by hand. Think "onboarding illustration set", "seasonal sticker pack", or "home-screen hero rotation". Each item resolves to a stable, long-cacheable public URL, returned in the order you set, so changing the bundle is a content edit — not an app release.

A catalog is a curated reference over your existing media, not a copy: the item URLs are the same public asset URLs every other media read returns.

Curate a catalog (MCP)

Agent: "Make an onboarding illustration set the app can fetch by slug"

amba_media_catalogs_create({
  project_id: "proj_xxx",
  slug: "onboarding-art",
  name: "Onboarding Illustrations"
})

amba_media_catalogs_add_item({
  project_id: "proj_xxx",
  catalog_id: "cat_xxx",
  media_id: "media_xxx",
  position: 0,
  caption: "Welcome screen"
})
ToolDescription
amba_media_catalogs_createCreate a slug-addressable catalog
amba_media_catalogs_listList catalogs with item counts
amba_media_catalogs_getGet a catalog and its ordered items
amba_media_catalogs_updateRename, re-slug, or publish/unpublish a catalog
amba_media_catalogs_deleteDelete a catalog (assets are untouched)
amba_media_catalogs_add_itemAdd a media asset to a catalog at a position
amba_media_catalogs_remove_itemRemove an asset from a catalog

Set is_public: false to keep a catalog as a draft — only public catalogs are readable from your app.

Read a catalog from your app (SDK)

const catalog = await Amba.media.catalog('onboarding-art');
 
for (const item of catalog.items) {
  // item.url is a stable, long-cacheable public URL
  render(item.url, item.caption);
}

The read is GET /v1/client/media/catalogs/:slug — it needs your client key but no signed-in user. The catalog JSON is client-cacheable (it sets Cache-Control: private, max-age=300, so repeat reads on the same device skip the round-trip). Each item.url is a stable CDN-served asset URL, so the asset bytes themselves are cached at the edge independently of the catalog index.

On this page