cdn.cactal.app.
Generated images use the same asset records, library, CDN URLs, responsive transformations, CMS fields, and deletion behavior as uploads. See Generate website images.
Kinds and limits
Every asset has akind that fixes its validation rules at upload time:
SVG is an
image only. An SVG is checked when it is finalized or imported: scripts, event handler attributes, foreignObject, DTD entities, external stylesheets, javascript: links, and image, feImage, or use references to anything other than #ids or data:image/ URIs are rejected with a validation error that names the reason. Embed raster images as data URIs and keep icons as plain paths.
Font, Lottie, and Rive files can be registered with mimeType: "application/octet-stream"; Cactal normalizes the type from the filename extension. Legacy font MIME aliases are normalized to the canonical font/woff2, font/woff, font/ttf, or font/otf type.
Only image assets can fill CMS image and gallery fields; file assets fill file fields. Uploads with a disallowed MIME type or an oversize byteSize are rejected at the first step, before any bytes move.
Descriptions and categories
Filenames are a poor index: a library ofimage.png files tells an agent nothing. Every asset therefore carries two optional, editable fields:
The category says what the image is, never where it is used. Uses such as hero, thumbnail, or avatar belong in the description because one asset serves many placements.
category on a file asset is rejected.
Supply both fields when you begin an upload. Agents uploading through the API or MCP have the context to write them and should. Generated images store their prompt as the description and accept an optional category per item. A PNG, JPEG, WebP, GIF, AVIF, BMP, or SVG image finalized without a description gets one, plus a category, written by a model in the background; that is best effort, never blocks the upload, never overwrites a description that already exists, and stops after 500 such descriptions per organization in a calendar month. An SVG is described through its rasterized preview; ICO images are not described automatically.
Both fields can be changed later with PATCH /v1/websiteAssets/{assetId}, which also renames the asset. Renaming never changes a delivery URL.
Colors
Every finalized image also gets a color palette, computed once from the original bytes and returned ascolors on list rows and single-asset reads, with transparentShare and translucentShare beside it. Each entry is { hex, share, accent }: up to eight colors sorted by the fraction of opaque pixels they cover. Flat colors that a designer chose, such as UI backgrounds, text, buttons, logo fills, and illustration shapes, are reported as their exact value, while photographs are summarized by cluster centroids. accent: true marks saturated colors that matter even at a small share, so a brand blue on one button is not lost behind a white page.
transparentShare is how much of the backdrop shows through, weighting each pixel by its transparency: 0 for an opaque image, roughly the background area for a cutout, 0.5 for a uniform 50 percent overlay. translucentShare is the fraction of pixels with partial alpha, which flags baked soft shadows, glows, fades, and glass that will look different on a dark surface. A painted checkerboard is opaque and reports 0 for both.
SVG assets are read rather than rendered, so their share is null. Images that cannot be decoded return colors: null, and file assets never carry a palette. Agents receive the same palette wherever they see the image, including attachments and the project context index, so they reproduce a reference’s exact colors instead of estimating them.
Upload flow
Uploading is three requests: register the upload, send the bytes, finalize.1
Begin the upload
POST /v1/websiteAssets with the file’s metadata and, ideally, a description and category:2
PUT the bytes
Send the raw bytes to
upload.url with upload.method and upload.headers. The URL expires 15 minutes after it is issued; if it lapses, begin a new upload.3
Finalize
POST /v1/websiteAssets/{assetId}/finalize verifies the object exists in storage and that its size matches the declared byteSize, then flips the status from pending to ready. For images, finalize also reads and stores the pixel dimensions and the color palette on the asset, and a raster image with no description gets one written in the background.POST /v1/websiteAssets/import, which fetches up to ten media URLs server-side (30 MiB each at most), applies the same validation and metadata fields as an upload, and returns ready assets. See Upload assets.
CDN delivery and image variants
Assets serve publicly fromcdn.cactal.app with Cache-Control: public, max-age=31536000, immutable — content at an asset URL never changes, so it caches for a year.
For transformable image types (image/png, image/jpeg, image/webp, image/avif), Cactal builds responsive variants at widths 256, 640, 960, 1280, 1920, and 2560, delivered as format=auto, quality=80 transforms. Single-asset reads and CMS image fields return the full delivery shape; list rows carry only url and srcSet so a page of results stays small:
url is the 1920-width default. SVG, GIF, BMP, and icon types are not transformed; they serve the original only, with srcSet: null and empty variants. file assets always serve their original bytes at url.
SVG previews
Image models cannot read SVG, so every SVG image also gets a rasterized PNG preview, rendered at ingest with the longest edge at 1024 pixels and stored as a hidden asset that never appears in listings.GET /v1/websiteAssets/{assetId} exposes it as previewUrl. Agents, MCP previews, view_image, chat attachments, generation references, and the favicon PNG fallback all read the preview while the website keeps serving the original SVG. previewUrl is null for other assets and for an SVG that could not be rendered within the time limit; such an SVG still uploads, but the agent cannot look at it.
Transparency and background removal
Cactal’s generation profiles support native transparency. Setbackground: "transparent" on the item and describe isolated artwork without a surface or shadow. Prompt wording alone does not guarantee transparency. transparentShare (see Colors) reports how much backdrop actually shows through; 0 means opaque even when the file has an alpha channel. translucentShare includes both soft effects and nearly opaque generated foreground pixels, so inspect the image on the intended background instead of treating that statistic alone as a defect.
POST /v1/websiteAssets/{assetId}/removeBackground creates a new PNG asset with the main subject isolated on a transparent background. The source must be a ready PNG, JPEG, WebP, GIF, or AVIF image and is left unchanged. The result is an ordinary asset with sourceAssetId pointing at the source, its own colors and transparentShare, the source category, and a description that names the source. Pass { "trim": true } to crop the result to the subject bounds. Soft shadows and reflections are removed, and images without a distinct subject give poor results, so look at the result before using it; over MCP the picture comes back with the response.
An image-generation item with "background": "transparent" stores its native PNG directly, with sourceAssetId: null, without an opaque duplicate or a background-removal pass. Use the removal operation above for an existing opaque asset. See Generate website images.
Background removal is not billed to AI credits. Each organization can create 10,000 background-removed images per calendar month.
Using assets
In CMS items — attach ready assets toimage, gallery, and file fields when creating or updating items. Image values accept "aB3x...", { "assetId": "aB3x...", "altText": "Product dashboard" }, or null to clear; galleries take ordered arrays of those; files take an asset id or { "assetId": "..." }. Reads return the hydrated shape above plus filename, mimeType, byteSize, dimensions, and altText. See CMS.
In source code: Use url as an image’s src, pass srcSet when present, and set sizes to match its rendered layout. Keep width and height or an equivalent aspect ratio to prevent layout shift. Use originalUrl only for downloads or intentional full-resolution access. For CSS backgrounds, use supplied variants with image-set() or select the smallest width that covers the rendered size at the intended pixel density.
As the site favicon — designate a ready PNG, SVG, or ICO asset as the website favicon with PUT /v1/websites/{websiteId}/favicon, or pick one under the site’s Settings in the dashboard. An SVG favicon is linked together with its PNG preview for browsers that ignore SVG icons. See Upload assets and Site favicon.
Assets are website-scoped: a CMS field can only reference assets uploaded to or generated for the same website. Reference-guided generation also accepts only ready image assets from that website.
Listing, searching, updating, and deleting
GET /v1/websiteAssets?websiteId=... lists ready assets newest first as compact rows (identity, description, category, dimensions, url, srcSet), with cursor/limit pagination (default 50, maximum 100) and four filters that combine:
kind:imageorfiletype: the broad family derived from the MIME type, one ofimage,video,audio,font, ordocument(everything else)category: one image categorysearch: each term is stemmed and prefix-matched against the filename, description, and category, soroasfinds “roastery” and2043findsIMG_2043.jpg. Multi-word searches require every term; when nothing matches, any term is enough
GET /v1/websiteAssets/{assetId} returns the full record with originalUrl, variants, and status. Over MCP, reading an image asset also returns the picture itself so an agent can look at it.
An upload begun with listed: false stays out of lists until the website uses it through a source edit, a CMS field, or the favicon; it still resolves by id, keeps its stable URLs, and is never described automatically. Cactal uses this for images attached to agent messages. See Attach project images.
PATCH /v1/websiteAssets/{assetId} changes filename, description, or category; pass only the fields to change, and null to clear a description or category.
DELETE /v1/websiteAssets/{assetId} archives rather than destroys: the asset leaves listings and can no longer be attached to CMS fields, but existing CDN URLs and already-attached CMS values keep serving so published pages never break. Deleting is idempotent.
To fully retire an image, replace it in the CMS items and source files that use it, then delete it. Deletion alone does not take the bytes off the CDN.
Constraints
Next steps
Upload assets
The full begin-PUT-finalize flow with error handling.
Generate images
Create or edit website imagery and store the results in this library.
Manage CMS content
Attach images, galleries, and files to content items.