Docs API reference
Upload presenter photos
Send a photo as a file or let Wunderframe fetch it from a URL.
A presenter’s photo appears wherever the presenter does in Wunderframe — in the library, on session pages and in your designs. Send it in one of two ways: let Wunderframe fetch it from a public address, or upload the file itself. Either way it is stored in your media library, so the photo keeps working when the original link goes away.
Both need an API key with the presenters scope and the presenter’s id.
From a URL
If your speaker portal already hosts the photos, send the address as JSON. Wunderframe downloads it and stores a copy.
curl -X PUT "https://www.wunderframe.com/api/v1/presenters/wkFRBzMq6POZ_qYJHvgQf/photo" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/photos/ada.jpg"
}'You can also include photo_url when you create, upsert or import presenters — it is fetched the same way.
From a file
Send the image itself as the request body with its Content-Type. The optional X-Filename header names the file in the media library.
curl -X PUT "https://www.wunderframe.com/api/v1/presenters/wkFRBzMq6POZ_qYJHvgQf/photo" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: image/jpeg" \
-H "X-Filename: ada-lovelace.jpg" \
--data-binary @ada-lovelace.jpgA whole folder of photos
When photos are named after your refs, one loop uploads them all:
// upload-photos.mjs — photos/speakerportal-1234.jpg → presenter with ref speakerportal/1234
import { readdir, readFile } from 'node:fs/promises';
import { extname, basename } from 'node:path';
const BASE = 'https://www.wunderframe.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.WUNDERFRAME_API_KEY}` };
const TYPES = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp' };
for (const file of await readdir('photos')) {
const type = TYPES[extname(file).toLowerCase()];
if (!type) continue;
const ref = basename(file, extname(file)).replace('-', '/');
// Find the presenter by your ref, then send the photo to its id.
const found = await fetch(`${BASE}/presenters/by-ref/${ref}`, { headers });
if (found.status === 404) { console.warn(`No presenter with ref ${ref}`); continue; }
const { id } = await found.json();
const res = await fetch(`${BASE}/presenters/${id}/photo`, {
method: 'PUT',
headers: { ...headers, 'Content-Type': type, 'X-Filename': file },
body: await readFile(`photos/${file}`)
});
console.log(res.ok ? `✓ ${ref}` : `✗ ${ref}: ${(await res.json()).error.message}`);
}The response
The call returns the updated presenter. photo_asset_id is the image in your media library, and photo_url is a signed address that works for about an hour — use it to show the photo, but do not store
it.
Replace or remove a photo
- Upload again to replace the photo. The previous image stays in the media library.
DELETE /presenters/{id}/photoremoves it from the presenter; the image also stays in the library.
Common errors
400 invalid— the URL is not https, cannot be reached, or answered with an error.404 not_found— no presenter with that id in the key’s workspace.413 too_large— the image is larger than 10 MB.415 unsupported— not a JPEG, PNG, WebP or GIF, or the wrongContent-Type.