Upload an image asset

Search Documentation

Search across all developer documentation

assets

Upload an image asset

POST /v1/assets
Auth required api.assets.manage

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.

200 The stored asset. 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.

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
409 The mutation is rejected by a state rule rather than a bad request. The code is conflict. application/problem+json
413 A query parameter, path value or body field failed validation. The body is a 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
422 A query parameter, path value or body field failed validation. The body is a 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
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json