Upload your own images
Your logo, your product photos, your background art: upload each once, get a URL on img.imageapis.com, and put that URL anywhere a template takes an image.
Why upload instead of linking
An image layer can point at any public URL, so why upload at all? Because a render fetches every image while it draws. A logo on your own site can be slow, blocked by a bot filter, moved, or rate limited, and each of those turns into a missing image in a card. An upload lives next to the renderer, answers fast from everywhere, and stays put until you delete it.
Uploads are free and don't count as images. An account holds up to 500 of them, each up to 10 MB.
Four ways to send one
The file itself. This is the shortest from a shell. name is optional:
curl -X POST "https://api.imageapis.com/v1/uploads?name=logo-dark.png" \
-H 'X-API-Key: YOUR_KEY' -H 'Content-Type: image/png' --data-binary @logo-dark.png
A form, the way a browser or curl -F sends files:
curl -X POST https://api.imageapis.com/v1/uploads -H 'X-API-Key: YOUR_KEY' -F file=@team-photo.jpg
A URL to copy. This pins an image that lives somewhere else, so later renders no longer depend on that host:
curl -X POST https://api.imageapis.com/v1/uploads -H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' -d '{"url": "https://example.com/brand/logo.svg"}'
Base64 in JSON. This is for clients that only speak JSON, including agents over MCP, where the tool is create_upload:
{ "data": "data:image/png;base64,iVBORw0KGgo…", "name": "badge.png" }
Every way returns the same record:
{
"id": "upl_8kq2wzm4tnb7hdc3vrex", "object": "upload",
"url": "https://img.imageapis.com/u/upl_8kq2wzm4tnb7hdc3vrex.png",
"name": "logo-dark.png", "format": "png", "content_type": "image/png",
"width": 512, "height": 512, "bytes": 18233, "created": "2026-10-11T15:02:11Z"
}
The type comes from the file's bytes, not its name or header, so a .png that is really a JPEG is stored as a JPEG. width and height are read from the file. That's handy for sizing a layer to the image's proportions.
Use it
The url goes anywhere a template takes an image: an image layer's src, a template's background_image, or an image inside your own HTML. In Python:
import os, requests
API = "https://api.imageapis.com"
H = {"X-API-Key": os.environ["IMAGEAPIS_KEY"]}
with open("logo-dark.png", "rb") as f:
logo = requests.post(f"{API}/v1/uploads", params={"name": "logo-dark.png"},
headers={**H, "Content-Type": "image/png"}, data=f).json()
card = requests.post(f"{API}/v1/images", headers=H, json={
"template": "tpl_…",
"modifications": [{"name": "logo", "src": logo["url"]}],
"variables": {"title": "Launch week starts Monday"},
}).json()
print(card["url"])
For a collection with a different photo per row, upload the photos first, then put their URLs in a CSV column named after the image layer:
title,photo.src,_sku
Walnut desk,https://img.imageapis.com/u/upl_….jpg,D-100
Oak shelf,https://img.imageapis.com/u/upl_….jpg,S-220
In the editor
In the editor, every image layer's src and the template's background have an Upload button. With your key pasted under "Save to your account", the file goes to POST /v1/uploads and the layer gets its URL. "Use one of my uploads" lists what is already on the key. Choosing an upload swaps it into the selected image layer, or adds a new layer at the upload's proportions. Without a key, an image under 350 KB can still be used. It gets embedded in the template as a data: URL, which works but makes the template JSON large.
What is accepted
- PNG, JPEG, WebP, GIF, AVIF and SVG, up to 10 MB and 50 megapixels.
- SVGs with scripts, event handlers,
javascript:links,foreignObjector entity declarations are refused. Uploaded SVGs are served with a policy that blocks scripts anyway. - URLs to copy must be public
httporhttpsaddresses. Private and internal addresses are refused.
List and delete
curl https://api.imageapis.com/v1/uploads -H 'X-API-Key: YOUR_KEY'
curl -X DELETE https://api.imageapis.com/v1/uploads/upl_… -H 'X-API-Key: YOUR_KEY'
Deleting an upload frees its slot. Its URL stops answering within a day, as caches expire. Templates that still use it render that layer empty, so swap the URL first.