Import an image from a URL

Search Documentation

Search across all developer documentation

assets

Import an image from a URL

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

Fetches the image at url into the asset library and returns the {{asset}} reference to use in its place — the fix an external_image notice offers. Only public addresses are fetched, with a 15-second timeout and at most three redirects.

It is an upload whose bytes come from a URL, so the format allowlist, the 5 MiB limit, the storage quota and 409 edge_cdn_not_provisioned are the upload's. Two things differ: the path is chosen for you — imported/<name>-<hash8>.<ext>, named by the stored bytes, so importing the same image again answers unchanged: true with the same path — and an SVG is converted to PNG rather than refused.

Your template is not changed. You own its source; replace source_url with reference there and push again.

Request body

Content type: application/json

url string required

An http://, https:// or protocol-relative image URL — typically the ref of an external_image notice.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The stored image. unchanged is true when it was already in the library. 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.

reference string required

The token to put where source_url was: {{asset "imported/logo-1a2b3c4d.png"}}.

source_url string required

The URL as given, to find and replace in your template source.

converted_from_svg boolean required

The URL served an SVG; the library holds a PNG rendering of it, because Gmail and most Outlook versions do not render SVG.

401 Missing, malformed, or unknown API key application/problem+json
402 The organization's plan does not allow this (code: plan_limit_exceeded). limit_type names the limit — a usage axis such as contacts, segments or active_workflows, or a feature the plan does not include (inboxes, platform_profile, audit_log, notification_presets). upgrade_url is the dashboard page that lifts it; overage_available says a Business organization can lift it by switching overage on instead. Retrying will not succeed until the plan changes or usage drops below the limit. 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
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