Upload an image asset
Uploads one image to the org's Edge CDN at a logical path — the string
templates reference as {{asset "static/logo.png"}} or as a relative
src attribute. Send either a multipart/form-data body with a file
part and a path field (the filename is used when path is omitted), or
a JSON body carrying the bytes as base64.
Content-addressed, so re-running a push is free. The object key is
the SHA-256 of the bytes, so uploading the same image at the same path
writes nothing and answers 200 with unchanged: true. Uploading
different bytes at the same path mints a new object and repoints the
path; the previous object is retained, so a template already deployed
against it keeps rendering the image it was deployed with.
Uploading does not redeploy anything. A deployed template embeds the
URL it was deployed against, so replacing an image leaves the templates
using it rendering the old one until they are deployed again.
referenced_by lists exactly those templates. For the same reason, a CI
push should upload images before writing templates.
The content type is decided by sniffing the bytes, never by the filename
or the supplied content_type: PNG, JPEG, GIF and WebP are accepted, and
anything else — including SVG, which can carry script and would be served
from your own CDN origin — is 422. The image must be at most 5 MiB. An
org with no active Edge CDN has nowhere to host the bytes and gets
409 edge_cdn_not_provisioned.
Request body
Content type: multipart/form-data
file string<binary> required The image bytes. At most 5 MiB; PNG, JPEG, GIF or WebP, decided by sniffing.
path string optional The logical path templates reference, e.g. static/logo.png. When omitted it defaults to the uploaded file's name with any directories stripped, so send it explicitly for a nested path.
Responses
Errors follow the RFC 7807 problem format — see the error reference.
unchanged is true when nothing was written. application/json id string<uuid> required source_path string required The logical path templates reference, e.g. static/logo.png. It is the path that was supplied on upload.
url string required Public CDN URL for the image, or "" when no Edge stack is provisioned yet.
content_hash string required Content-addressed hash of the image bytes.
content_type string required MIME type, e.g. image/png.
size_bytes integer<int64> required width integer required Pixel width, or 0 when unknown (e.g. SVG).
height integer required Pixel height, or 0 when unknown (e.g. SVG).
created_at string<date-time> required referenced_by array<object> required The templates whose body references this asset's path. Always present: an empty array means the scan ran and found none, which is what a caller about to delete the asset needs to know.
Each entry in referenced_by:
id string<uuid> required name string required tags array<object> optional Org-side tags/folders applied to this asset (managed via the dashboard). Filter the collection with ?tag=.
Each entry in tags:
id string<uuid> required name string required Full folder path (e.g. Marketing/Newsletters).
key string required Normalized slug handle of the name.
color string optional Chip colour token (chart-1..chart-5), or empty.
description string optional created_at string<date-time> required updated_at string<date-time> required unchanged boolean required True when the same bytes were already stored at this path — no object was written and no row was touched. A CI push that re-runs on every commit sees this on every unchanged image.
code is conflict.
application/problem+json validation_failed Problem. When the refusal is about a
reference — a segment key, template slug, topic or attribute name the
organization does not have — it additionally carries validation_code,
field, line/column, missing, candidates and next_step, so a
client can correct the call without a second round of guessing. See
ValidationProblem.
application/problem+json validation_failed Problem. When the refusal is about a
reference — a segment key, template slug, topic or attribute name the
organization does not have — it additionally carries validation_code,
field, line/column, missing, candidates and next_step, so a
client can correct the call without a second round of guessing. See
ValidationProblem.
application/problem+json code is internal_error. The
request_id field can be quoted to SendOps support to investigate.
application/problem+json