Skip to content

Image Library

The public API can manage the same image library the designer’s My Images panel shows: every image your organization has uploaded, referenced in a design as zandovi://images/{id}. These endpoints need the images:read or images:write scope (see Scopes) and consume no render quota — uploading, listing and deleting images is free.

GET /api/v1/images

Images visible to the credential, newest first: those its account uploaded, plus any other member’s images marked SHARED. Needs images:read.

Query parameters

ParamTypeDefaultDescription
cursorstring—The nextCursor from a previous page. Omit for the first page.
limitinteger20Items per page (max 50).
qstring—Case-insensitive substring of the image’s name.
Terminal window
curl "https://app.zandovi.com/api/v1/images?limit=20" \
-H "X-Api-Key: $ZANDOVI_API_KEY"

Response — 200 OK:

{
"items": [
{
"id": "0192c3f0-8d6a-7b1c-9f4e-2a5b6c7d8e9f",
"uri": "zandovi://images/0192c3f0-8d6a-7b1c-9f4e-2a5b6c7d8e9f",
"name": "logo.png",
"mimeType": "image/png",
"width": 512,
"height": 512,
"sizeBytes": 48213,
"visibility": "PRIVATE",
"createdAt": "2026-09-20T14:30:00Z",
"previewUrl": "https://app.zandovi.com/api/v1/images/0192c3f0-8d6a-7b1c-9f4e-2a5b6c7d8e9f/preview"
}
],
"nextCursor": null
}

No images yet → {"items": [], "nextCursor": null}. An unrecognised cursor (not one this listing issued) is a 400 INVALID_CURSOR.

GET /api/v1/images/{id}

One image in the shape above. Needs images:read. An id outside the credential’s visibleTo set (another organization’s, or another member’s PRIVATE image) is 404 IMAGE_NOT_FOUND.

GET /api/v1/images/{id}/preview

Streams the stored preview (longest edge 600 px; falls back to the original for an image uploaded before previews existed) with its real content type. Needs images:read. Cacheable: Cache-Control: private, max-age=3600 and an ETag — send If-None-Match to get a 304. Consumes no quota and is never rate-limited beyond the standard per-key limiter, so it is safe to fetch for every item in a listing.

POST /api/v1/images

Adds an image to the organization’s library. Needs images:write. Two request shapes, mutually exclusive:

  • multipart/form-data, part file (the bytes) plus optional visibility and name fields.
  • application/json, {url, visibility?, name?, provider?, imageId?, downloadUrl?} — the server fetches a public http(s) URL itself. provider, imageId and downloadUrl are for stock-photo attribution, below.

Either way the bytes must be a JPEG, PNG, GIF or WebP of at most 5 MB, typed by their actual bytes (not the declared content type). Send an Idempotency-Key from automation, as for template writes (see Idempotency).

Terminal window
# By URL
curl -i https://app.zandovi.com/api/v1/images \
-H "X-Api-Key: $ZANDOVI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://images.example.com/summer-hero.jpg", "visibility": "SHARED" }'
# By file
curl -i https://app.zandovi.com/api/v1/images \
-H "X-Api-Key: $ZANDOVI_API_KEY" \
-F "visibility=SHARED"

Response — 201 Created with a Location header and the item shape from List images, or 200 OK with "reused": true when you uploaded or saved an image from the same source yourself, matched by provider + imageId when both are given, else by url (no second copy is stored). A teammate’s copy, shared or private, is never reused: a new copy is stored for you.

StatusCause
400 VALIDATION_ERRORNo file part or url, a visibility other than PRIVATE or SHARED, or malformed JSON
400 URL_NOT_ALLOWEDurl is not a public http(s) address (a private/loopback/metadata address, a zandovi:// uri or a data: URL)
413 IMAGE_TOO_LARGEThe bytes are over 5 MB
415 UNSUPPORTED_IMAGE_TYPEThe sniffed bytes are not a JPEG, PNG, GIF or WebP
422 IMAGE_UNREACHABLEThe url could not be fetched

Uploads default to visibility: "PRIVATE": only the uploading account sees the image, in the designer and in List images. This matches how the designer itself uploads — but it has one consequence worth knowing before you script it: if a template referencing a PRIVATE image is opened by a teammate in the designer, the image shows as unresolved for them, because the resolver only serves a PRIVATE image back to its uploader. Pass visibility: "SHARED" for any image a template’s other editors need to see.

Unsplash, Pexels and Pixabay require attributing the photographer and, for Unsplash, pinging their API when a photo is used — obligations the MCP server’s stock search satisfies for you server-side. Every search hit is kept in a 24-hour cache keyed by its URL, and whenever the server ingests a URL — through this endpoint, or when a design’s image.src references a stock URL directly — it records an attribution row: the request’s provider/imageId/downloadUrl are honoured first, then the cached hit fills in the photographer (and downloadUrl if you omitted it), and failing both the provider is inferred from the URL’s host. So, to keep the obligations satisfied when you upload a stock photo yourself: pass the hit through right after searching — url set to the hit’s previewUrl (or url) — and the server records the photographer credit and fires the Unsplash download ping exactly once; or include provider, imageId and downloadUrl yourself, which still records the provider and pings after the cache expires (the photographer credit is only available from the cache). A bare URL sent more than 24 hours after the search, or one that no search ever returned, gets a provider-only row: no photographer credit and no ping.

DELETE /api/v1/images/{id}

Deletes an image your account uploaded. Needs images:write.

StatusCause
204 No ContentDeleted
404 IMAGE_NOT_FOUNDNo such image uploaded by your account
409 IMAGE_IN_USEA template anywhere in the organization still references the image; details.usedInTemplates lists {id, name} for each. Delete is refused, even for a template you don’t own — remove the reference first.
Terminal window
curl -X DELETE "https://app.zandovi.com/api/v1/images/$IMAGE_ID" \
-H "X-Api-Key: $ZANDOVI_API_KEY" \
-H "Idempotency-Key: delete-old-logo"

Put the item’s uri (zandovi://images/{id}) in an image element’s src. The renderer resolves it under the same visibility rule as this API: your organization, and either your own account or a SHARED image. See Authoring Templates for the full design document shape.