---
title: list_assets, upload_asset
description: Upload, list, replace, and get assets.
section: MCP Reference
order: 6
---

# list_assets, upload_asset

File uploads, replacement, and management.

## Size Limits

Effective max = min(per-type cap, remaining website storage):

| Content type | Cap |
|---|---|
| Images | 5MB |
| Fonts | 2.5MB |
| Video | 15MB |
| Audio | 15MB |
| Other (PDF, docs, archives) | 10MB |

## Supported Types

Images (JPEG, PNG, GIF, WebP, SVG, AVIF, ICO, BMP, TIFF), Documents (PDF, DOCX, XLSX, PPTX, TXT, RTF, CSV), Fonts (WOFF, WOFF2, TTF, OTF), CSS, JS, Audio, Video, Archives.

## Actions

### list

List all assets for a website. Pass `asset_id` to retrieve a single asset.

**Parameters:**
- `website` — Website subdomain, domain, or UUID
- `asset_id` — Optional asset UUID to retrieve a single asset
- `limit` — Max results (default 50, max 100)
- `offset` — Items to skip (default 0)

## Upload

- **URL** (`upload_asset` with `url`): preferred for any publicly accessible file. ≤5MB images / 2.5MB fonts / 15MB video & audio / 10MB other.
- **Base64** (`upload_asset` with `base64`): tiny files only (≤20KB decoded). Suitable for agent-generated SVG icons, QR codes, simple graphics. LLMs silently corrupt and truncate larger base64 — do not attempt. Use `request_upload_url` instead.
- **Session-based upload URL** (`request_upload_url`): for any size up to the per-type limits when you have local/non-URL content. Returns a short-lived URL to PUT bytes to — the asset is created server-side on PUT.

## Replace an existing asset

Pass `asset_id` to `upload_asset` (URL or base64 mode) or `request_upload_url` to replace an existing asset's bytes instead of creating a new one. The asset's `id`, URL, and filename are preserved — only the file content changes.

> **Replace is permanent.** The old file's bytes are purged immediately — there is no undo and no version history. Every page that references the asset serves the new content right away, including pinned pages and older page versions. If you need the old file to stay live somewhere, upload the new file as a **new** asset and update only the references you want changed.

**Rules:**
- The new file's `content_type` **must match** the existing asset's content type. Cross-format replacement (PNG → JPEG) is rejected. To convert formats, delete and re-upload.
- The filename is preserved from the original — the new file's name is discarded.
- All variants (WebP, AVIF, sized) are regenerated asynchronously by the optimizer.
- The `relative_url` returned in the response includes a `?v=<token>` cache-bust query param. Server-side, every URL for an asset resolves to the **current** bytes — including old `?v=` tokens and URLs with no `?v=` at all (the token only busts the browser/CDN cache). The token flips on replace so the new content is picked up immediately; a browser-cached copy of an old URL may still serve stale bytes until that cache entry expires.
- For `upload_asset` with `base64` + `asset_id`, `content_type` is required (use the existing asset's content type).
- For `request_upload_url` with `asset_id`, the `Content-Type` header on the PUT must match the existing asset's content type.

**Replace via URL:**
```
upload_asset(website: "...", url: "https://example.com/fresh-logo.png", asset_id: "...")
```

**Replace via base64:**
```
upload_asset(website: "...", base64: "...", filename: "logo.png", content_type: "image/png", asset_id: "...")
```

**Replace via upload URL (large local files):**
```
request_upload_url(website: "...", asset_id: "...")  # → returns upload_url bound to the target asset
# Then PUT to the URL with ?filename=... and matching Content-Type
```

If the target asset is deleted between `request_upload_url` and the PUT, the PUT returns 422 with `"Asset not found"`.

> **Storage-exceeded sites:** `request_upload_url` is blocked when the website is over its storage limit (a replace would temporarily add bytes before the old blob is purged). To replace an asset on a storage-locked site, use `upload_asset` with `url` instead — it has no storage pre-check and the Replacer credits the freed bytes.

## Video & Audio

PageWeave hosts short video and audio clips up to 15MB per file. Files are served as-is (no transcoding or adaptive streaming).

For longer or higher-quality video, use a dedicated video host and embed it via `<iframe>` or `<video>`.

> **GDPR note:** Embedding YouTube or Vimeo loads tracking cookies on page load (ePrivacy Art 5(3)), and the embedding site becomes a joint data controller (CJEU C-40/17). Consider EU-hosted, cookieless alternatives:
> - [Mave](https://www.mave.io/) — cookieless, EU-hosted, ISO 27001 certified
> - [Jet-Stream](https://jet-stream.com/) — EU sovereign cloud, EDPS-audited GDPR compliance
>
> Direct hosting on PageWeave (EU Hetzner, no cookies, no tracking) is itself the most privacy-friendly option for short clips.

## Format Optimization

Raster images are automatically optimized after upload:

- WebP and AVIF variants created asynchronously in the background
- Use the original `relative_url` in HTML — the browser receives the best format it supports via Accept header negotiation
- No `.webp` or `.avif` suffix needed

## Responsive Images

Size variants (320w, 640w, 1024w, 1920w) are automatically generated and injected as `srcset` attributes. Use standard `<img src="...">` without `srcset`.

## Lazy Loading

For images below the fold, add `loading="lazy"` to improve page performance. Hero/first images should remain eager (default browser behavior).

## Image Dimensions

`width` and `height` attributes are auto-injected from asset metadata to prevent layout shift (CLS). If you need CSS-controlled sizing (e.g. `class="w-full"`, `class="h-8"`, or inline `style="width:..."`), add `data-no-dimensions` to skip auto-injection and avoid aspect-ratio distortion.

## OAuth Scopes

| Action | Scope |
|--------|-------|
| list | `asset:read` |
| upload | `asset:write` |
| replace | `asset:write` |
