Use your own images and fonts
Upload a file once, then point at it with the reference string the upload response returns.
There are two kinds, and the upload’s category decides which one you get:
| Reference | Goes in | Resolved as |
|---|---|---|
asset:<id> | an image_url override, or a template image layer’s src | the image bytes, inlined server-side |
font:<id> | a fontFamily override, or a template text layer’s style.fontFamily | an injected @font-face block |
The renderer resolves both from your account server-side, so you never need a public URL. For
images you can also pass a base64 data:image/* URI directly.
References are private to the account that uploaded them. A reference to another account’s upload,
or to one you have since deleted, is not an error: the render completes with an asset_unresolved warning, the image layer is left blank, and the text layer falls back to its
default font. The App → Assets page lists which templates use each upload, so check it before
deleting.
Prefer a UI?
Manage your uploads and copy references under App → Assets.Upload, list, and reference
Asset references
# 1. Upload a file (multipart). 'name' is optional.
curl -X POST https://yourdomain.com/api/uploads \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F '[email protected]' \
-F 'name=hero shot'
# → 201 { "id": "...", "name": "hero shot", "reference": "asset:...", ... }# 2. List your uploads to find their ids / references later
curl https://yourdomain.com/api/uploads \
-H 'Authorization: Bearer YOUR_API_KEY'
# → { "uploads": [{ "id": "...", "name": "hero shot", "reference": "asset:..." }], "total": 1 }# 3. Reference it when generating (works in image_url overrides and layer src)
{
"templateId": "your-template-id",
"layers": {
"photo": { "image_url": "asset:PASTE_UPLOAD_ID" }
}
}Custom fonts (Pro)
Upload a font file and reference it in a text layer’s fontFamily with its font:<id> reference. The same value works in three places: a per-render layers override on /api/generate and the batch endpoint, the generate_render and update_layer MCP tools, and the template JSON
itself. The editor’s font picker writes the same marker when you choose an uploaded font.
One static face per upload, so upload bold or italic variants as separate files and reference each by its own id. There is no in-place replacement: to swap a font, upload the new file and update the templates that use the old id.
Custom fonts
# Font uploads require the Pro plan. Max 5MB, OTF/TTF/WOFF/WOFF2 only
# (validated by content, not filename).
curl -X POST https://yourdomain.com/api/uploads \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F '[email protected]' \
-F 'category=font' \
-F 'name=Brand Sans'
# → 201 { "id": "...", "name": "Brand Sans", "category": "font", "reference": "font:...", ... }# Per-render override: fontFamily sits directly on the override object
# (overrides are flat, there is no nested "style" key)
{
"templateId": "your-template-id",
"layers": {
"title": { "fontFamily": "font:PASTE_UPLOAD_ID" }
}
}# Or bake it into the template itself: a text layer's style.fontFamily
{
"type": "text",
"name": "title",
"content": "Hello",
"style": { "fontFamily": "font:PASTE_UPLOAD_ID", "fontSize": 64, "color": "#111111" }
}Non-Pro accounts
Custom fonts are a Pro plan feature. Uploading a font file on a non-Pro account is rejected with a 403 at upload time. If a template'sfont:<id> reference is
rendered by an account that has since downgraded from Pro, the render still completes,
the custom face is skipped and the layer falls back to its default font, with a custom_fonts_blocked warning in the response's warnings array. The public,
unauthenticated /api/render URL endpoint never resolves custom fonts, whatever the
owner's plan, so templates meant for Render URLs should use the built-in font list.