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.
List images
Section titled “List images”GET /api/v1/imagesImages visible to the credential, newest first: those its account uploaded, plus any other member’s images marked SHARED. Needs images:read.
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
cursor | string | — | The nextCursor from a previous page. Omit for the first page. |
limit | integer | 20 | Items per page (max 50). |
q | string | — | Case-insensitive substring of the image’s name. |
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 an image
Section titled “Get an image”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 an image’s preview
Section titled “Get an image’s preview”GET /api/v1/images/{id}/previewStreams 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.
Upload an image
Section titled “Upload an image”POST /api/v1/imagesAdds an image to the organization’s library. Needs images:write. Two request shapes, mutually exclusive:
multipart/form-data, partfile(the bytes) plus optionalvisibilityandnamefields.application/json,{url, visibility?, name?, provider?, imageId?, downloadUrl?}— the server fetches a publichttp(s)URL itself.provider,imageIdanddownloadUrlare 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).
# By URLcurl -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 filecurl -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.
| Status | Cause |
|---|---|
400 VALIDATION_ERROR | No file part or url, a visibility other than PRIVATE or SHARED, or malformed JSON |
400 URL_NOT_ALLOWED | url is not a public http(s) address (a private/loopback/metadata address, a zandovi:// uri or a data: URL) |
413 IMAGE_TOO_LARGE | The bytes are over 5 MB |
415 UNSUPPORTED_IMAGE_TYPE | The sniffed bytes are not a JPEG, PNG, GIF or WebP |
422 IMAGE_UNREACHABLE | The url could not be fetched |
Visibility
Section titled “Visibility”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.
Stock-photo attribution
Section titled “Stock-photo attribution”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 an image
Section titled “Delete an image”DELETE /api/v1/images/{id}Deletes an image your account uploaded. Needs images:write.
| Status | Cause |
|---|---|
204 No Content | Deleted |
404 IMAGE_NOT_FOUND | No such image uploaded by your account |
409 IMAGE_IN_USE | A 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. |
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"Using an uploaded image in a design
Section titled “Using an uploaded image in a design”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.