Docs API reference
Engagements
Live engagements: the sticky notes, questions and polaroid walls guests post to from their phones. Any system can post into one like a guest — a photo booth, a kiosk, an app — through the same moderation, and get a signed take-home link for each photo or video. Live polls (multiple choice, word clouds, ratings) are prepared and run under /engagements/{id}/polls and /polls/{poll_id}. The posting flow: POST /engagements/{id}/uploads, PUT the file to the upload_url, then POST /engagements/{id}/posts; poll GET /engagements/{id}/posts/{post_id} until share_ready before you show the take-home QR code.
Every call needs an API key in the Authorization header. New to the API? Start with
the API overview.
- GET List an engagement’s quizzes /engagements/{id}/quizzes
- POST Create a quiz /engagements/{id}/quizzes
- GET Get a quiz /quizzes/{quiz_id}
- PATCH Update a quiz /quizzes/{quiz_id}
- DELETE Delete a quiz /quizzes/{quiz_id}
- POST Start a game /quizzes/{quiz_id}/games
- GET Get a game’s results /quiz-games/{game_id}
- POST Run a game /quiz-games/{game_id}/{action}
- GET List polls /engagements/{id}/polls
- POST Create a poll /engagements/{id}/polls
- PATCH Change a poll /polls/{poll_id}
- DELETE Delete a poll /polls/{poll_id}
- POST Run a poll /polls/{poll_id}/{action}
- GET Get a poll’s results /polls/{poll_id}/results
- GET List engagements /engagements
- GET Get an engagement /engagements/{id}
- POST Get an upload URL /engagements/{id}/uploads
- POST Post like a guest /engagements/{id}/posts
- GET Get a post’s status /engagements/{id}/posts/{post_id}
List an engagement’s quizzes
/engagements/{id}/quizzes engagements:readIts quizzes with their rounds, questions and right answers, and the games played (newest first).
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Responses
- 200 The quizzes and games.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
itemsobject[]10 fields in
itemsidstringengagement_idstringtitlestringroundsobject[]3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
questionsintegerproblemstring or nullWhy it cannot be played yet.
problemsstring[]A spreadsheet import: the rows that could not be read.
created_atintegerupdated_atinteger
gamesobject[]6 fields in
gamesidstringquiz_idstringtitlestringphasestringstarted_atintegerended_atinteger or null
curl "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/quizzes" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"items": [
{
"id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"rounds": [
{
"id": "r1",
"title": "Warm-up",
"questions": [
{
"id": "cap",
"kind": "choice",
"text": "Capital of Switzerland?",
"options": [
{
"id": "o1",
"label": "Zurich"
},
{
"id": "o2",
"label": "Bern"
},
{
"id": "o3",
"label": "Geneva"
}
],
"answer": {
"option_id": "o2"
},
"points": 1000,
"time_s": 20
}
]
}
],
"settings": {
"mode": "teams",
"captain": false,
"leaderboard": "round",
"streak": true,
"auto_advance": false,
"read_ms": 3000,
"reveal_ms": 5000,
"board_ms": 8000
},
"questions": 1,
"problem": null,
"created_at": 1791468000000,
"updated_at": 1791468000000
}
],
"games": [
{
"id": "string",
"quiz_id": "string",
"title": "string",
"phase": "string",
"started_at": 0,
"ended_at": null
}
]
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Create a quiz
/engagements/{id}/quizzes engagements:writeFrom rounds of questions, or from a spreadsheet (csv: rows it cannot read come back in problems, the rest is imported).
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Request body required
titlestringUp to 80 characters
roundsobject[]Up to 12 items
3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
csvstringA spreadsheet instead of
rounds.
Responses
- 201 The quiz.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (201)
idstringengagement_idstringtitlestringroundsobject[]3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
questionsintegerproblemstring or nullWhy it cannot be played yet.
problemsstring[]A spreadsheet import: the rows that could not be read.
created_atintegerupdated_atinteger
curl -X POST "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/quizzes" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Friday quiz night",
"settings": {
"mode": "teams",
"leaderboard": "round"
},
"rounds": [
{
"title": "Warm-up",
"questions": [
{
"kind": "choice",
"text": "Capital of Switzerland?",
"options": [
"Zurich",
"Bern",
"Geneva"
],
"answer": {
"index": 1
}
},
{
"kind": "number",
"text": "Year of the Moon landing?",
"answer": {
"number": 1969,
"tolerance": 2
},
"unit": "year"
}
]
}
]
}'{
"id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"rounds": [
{
"id": "r1",
"title": "Warm-up",
"questions": [
{
"id": "cap",
"kind": "choice",
"text": "Capital of Switzerland?",
"options": [
{
"id": "o1",
"label": "Zurich"
},
{
"id": "o2",
"label": "Bern"
},
{
"id": "o3",
"label": "Geneva"
}
],
"answer": {
"option_id": "o2"
},
"points": 1000,
"time_s": 20
}
]
}
],
"settings": {
"mode": "teams",
"captain": false,
"leaderboard": "round",
"streak": true,
"auto_advance": false,
"read_ms": 3000,
"reveal_ms": 5000,
"board_ms": 8000
},
"questions": 1,
"problem": null,
"created_at": 1791468000000,
"updated_at": 1791468000000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Get a quiz
/quizzes/{quiz_id} engagements:readParameters
quiz_idstring required pathThe quiz id (from
GET /engagements/{id}/quizzes).
Responses
- 200 The quiz.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstringengagement_idstringtitlestringroundsobject[]3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
questionsintegerproblemstring or nullWhy it cannot be played yet.
problemsstring[]A spreadsheet import: the rows that could not be read.
created_atintegerupdated_atinteger
curl "https://www.wunderframe.com/api/v1/quizzes/quiz_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"rounds": [
{
"id": "r1",
"title": "Warm-up",
"questions": [
{
"id": "cap",
"kind": "choice",
"text": "Capital of Switzerland?",
"options": [
{
"id": "o1",
"label": "Zurich"
},
{
"id": "o2",
"label": "Bern"
},
{
"id": "o3",
"label": "Geneva"
}
],
"answer": {
"option_id": "o2"
},
"points": 1000,
"time_s": 20
}
]
}
],
"settings": {
"mode": "teams",
"captain": false,
"leaderboard": "round",
"streak": true,
"auto_advance": false,
"read_ms": 3000,
"reveal_ms": 5000,
"board_ms": 8000
},
"questions": 1,
"problem": null,
"created_at": 1791468000000,
"updated_at": 1791468000000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Update a quiz
/quizzes/{quiz_id} engagements:writeOnly the parts you send change (csv replaces the rounds). A game already running keeps the quiz as it was when it started.
Parameters
quiz_idstring required pathThe quiz id (from
GET /engagements/{id}/quizzes).
Request body required
titlestringUp to 80 characters
roundsobject[]Up to 12 items
3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
csvstringA spreadsheet instead of
rounds.
Responses
- 200 The quiz.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstringengagement_idstringtitlestringroundsobject[]3 fields in
roundsidstringtitlestringquestionsobject[]10 fields in
questionsidstringShort and stable; made for you when left out.
kindstring requiredOne of
choice,true_false,number,ordertextstring requiredUp to 200 characters
image_asset_idstring or nullA picture round: an image from the media library.
optionsobject[]2 fields in
optionsidstringlabelstringUp to 80 characters
answerobject5 fields in
answeroption_idstringindexintegerOn input: the right option by position, from 0.
valuebooleannumbernumbertolerancenumber
unitstringUp to 16 characters
pointsintegerFor a right answer at once (default 1000); right at the last moment scores half.
0–5000
time_sintegerSeconds to answer (default 20).
5–120
partialboolean
settingsobjectHow the quiz is played.
8 fields in
settingsmodestringOne of
solo,teamscaptainbooleanTeams: the captain’s answer counts (else the team’s first).
leaderboardstringOne of
question,round,endstreakbooleanA bonus for right answers in a row.
auto_advancebooleanThe game runs by itself: reveal, leaderboard, next.
read_msinteger0–15000
reveal_msinteger2000–30000
board_msinteger3000–60000
questionsintegerproblemstring or nullWhy it cannot be played yet.
problemsstring[]A spreadsheet import: the rows that could not be read.
created_atintegerupdated_atinteger
curl -X PATCH "https://www.wunderframe.com/api/v1/quizzes/quiz_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Friday quiz night — week 2",
"settings": {
"auto_advance": true
}
}'{
"id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"rounds": [
{
"id": "r1",
"title": "Warm-up",
"questions": [
{
"id": "cap",
"kind": "choice",
"text": "Capital of Switzerland?",
"options": [
{
"id": "o1",
"label": "Zurich"
},
{
"id": "o2",
"label": "Bern"
},
{
"id": "o3",
"label": "Geneva"
}
],
"answer": {
"option_id": "o2"
},
"points": 1000,
"time_s": 20
}
]
}
],
"settings": {
"mode": "teams",
"captain": false,
"leaderboard": "round",
"streak": true,
"auto_advance": false,
"read_ms": 3000,
"reveal_ms": 5000,
"board_ms": 8000
},
"questions": 1,
"problem": null,
"created_at": 1791468000000,
"updated_at": 1791468000000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Delete a quiz
/quizzes/{quiz_id} engagements:writeIts games keep their results. Refused (409) while a game of it runs.
Parameters
quiz_idstring required pathThe quiz id (from
GET /engagements/{id}/quizzes).
Responses
- 204 Deleted.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
curl -X DELETE "https://www.wunderframe.com/api/v1/quizzes/quiz_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"204 — no body.
Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Start a game
/quizzes/{quiz_id}/games engagements:writeA lobby: guests join on the engagement’s phone page, the screens show the QR code and the names flowing in. A game running on the engagement ends first. Never start one nobody asked for.
Parameters
quiz_idstring required pathThe quiz id (from
GET /engagements/{id}/quizzes).
Responses
- 201 The game.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (201)
idstringquiz_idstringengagement_idstringtitlestringphasestringOne of
lobby,question,locked,reveal,leaderboard,podium,endedqintegerThe question, counted across rounds from 0; -1 in the lobby.
totalintegerautobooleanopens_atinteger or nulllocks_atinteger or nullstarted_atintegerended_atinteger or null
curl -X POST "https://www.wunderframe.com/api/v1/quizzes/quiz_id/games" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "qg_Zp4dW1Q2mK7vB3xYc",
"quiz_id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"phase": "question",
"q": 0,
"total": 12,
"auto": false,
"opens_at": 1791468003000,
"locks_at": 1791468023000,
"started_at": 1791467900000,
"ended_at": null
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Get a game’s results
/quiz-games/{game_id} engagements:readThe leaderboard and each question’s result (the right answer, counts, the fastest). ?format=csv answers the final leaderboard as CSV.
Parameters
game_idstring required pathThe game id (from
POST /quizzes/{quiz_id}/games).formatstring queryOne of
json,csv
Responses
- 200 The results.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
gameobjectleaderboardobject[]5 fields in
leaderboardnamestringscoreintegercorrectintegerrankintegerplayersintegerTeams only.
questionsobject[]
curl "https://www.wunderframe.com/api/v1/quiz-games/game_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"game": {
"id": "qg_Zp4dW1Q2mK7vB3xYc",
"title": "Friday quiz night",
"mode": "teams",
"phase": "ended",
"started_at": 1791467900000,
"ended_at": 1791471500000,
"players": 38,
"teams": 9
},
"leaderboard": [
{
"id": "tm_1",
"name": "The Quizzly Bears",
"score": 8450,
"correct": 10,
"streak": 3,
"rank": 1,
"prev_rank": 2,
"last": {
"answered": true,
"correct": true,
"points": 910
},
"players": 4
}
],
"questions": [
{
"index": 0,
"round": "Warm-up",
"text": "Capital of Switzerland?",
"kind": "choice",
"question_id": "cap",
"answer": {
"option_id": "o2"
},
"counts": {
"o1": 11,
"o2": 25,
"o3": 2
},
"answered": 38,
"right": 25,
"fastest": {
"name": "Ada · The Quizzly Bears",
"ms": 2140
}
}
]
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Run a game
/quiz-games/{game_id}/{action} engagements:writeLike the Quizmaster: begin (the first question), lock (stop answers early), reveal (the right answer and the scores), leaderboard, next (the next question, or the podium after the last), podium, end, and auto_on / auto_off (the game runs by itself). A question locks by itself when its time is up. 409 when the move does not fit where the game is.
Parameters
game_idstring required pathThe game id (from
POST /quizzes/{quiz_id}/games).actionstring required pathOne of
begin,lock,reveal,leaderboard,next,podium,end,auto_on,auto_off
Responses
- 200 The game as it is now.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstringquiz_idstringengagement_idstringtitlestringphasestringOne of
lobby,question,locked,reveal,leaderboard,podium,endedqintegerThe question, counted across rounds from 0; -1 in the lobby.
totalintegerautobooleanopens_atinteger or nulllocks_atinteger or nullstarted_atintegerended_atinteger or null
curl -X POST "https://www.wunderframe.com/api/v1/quiz-games/game_id/action" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "qg_Zp4dW1Q2mK7vB3xYc",
"quiz_id": "qz_7vB3xYcZp4dW1Q2mK",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"title": "Friday quiz night",
"phase": "question",
"q": 0,
"total": 12,
"auto": false,
"opens_at": 1791468003000,
"locks_at": 1791468023000,
"started_at": 1791467900000,
"ended_at": null
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}List polls
/engagements/{id}/polls engagements:readThe engagement’s live polls, in the order they were prepared, each with where its run stands.
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Responses
- 200 The polls.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
itemsobject[]20 fields in
itemsidstring requiredengagement_idstring requiredorderintegertypestring requiredOne of
choice,word_cloud,ratingquestionstring requiredUp to 200 characters
optionsobject[]Multiple choice: 2–10 answers. The id never changes when the label does.
2 fields in
optionsidstringlabelstringUp to 80 characters
max_choicesinteger1 = one answer; more = up to that many.
scaleobject or nullA rating: 1–5 or 1–10 as numbers, stars or faces (stars and faces are 1–5).
4 fields in
scaleminintegermaxintegerOne of
5,10stylestringOne of
numbers,stars,emojilabelsstring[] or null
words_per_guestinteger1–3
resultsstringThe room sees the results as they come in, or once revealed.
One of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changebooleanstatestring requiredOne of
draft,live,closedrevealedbooleanrunintegerBumped by
reset: earlier votes stop counting.launched_atinteger or nullclosed_atinteger or nullcreated_atintegerupdated_atinteger
curl "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/polls" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"items": [
{
"id": "pol_8Hk2mQ4xZ1",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"order": 0,
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
{
"id": "a1b2",
"label": "The keynote"
},
{
"id": "c3d4",
"label": "The workshop"
}
],
"max_choices": 1,
"scale": null,
"words_per_guest": 1,
"results": "on_reveal",
"phone_results": "after_vote",
"number_format": "percent",
"allow_change": true,
"state": "live",
"revealed": false,
"run": 1,
"launched_at": 1790150400000,
"closed_at": null,
"created_at": 1790140000000,
"updated_at": 1790150400000
}
]
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Create a poll
/engagements/{id}/polls engagements:writeA new poll (a draft) at the end of the engagement’s list. Multiple choice takes 2–10 options (strings, or { id, label } to keep an id); a rating a scale; a word cloud words_per_guest (1–3). The engagement needs polls switched on to run it.
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Request body required
typestringOne of
choice,word_cloud,ratingquestionstringUp to 200 characters
optionsstring or object[]max_choicesintegerscaleobjectwords_per_guestintegerresultsstringOne of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changeboolean
Responses
- 201 The new poll.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (201)
idstring requiredengagement_idstring requiredorderintegertypestring requiredOne of
choice,word_cloud,ratingquestionstring requiredUp to 200 characters
optionsobject[]Multiple choice: 2–10 answers. The id never changes when the label does.
2 fields in
optionsidstringlabelstringUp to 80 characters
max_choicesinteger1 = one answer; more = up to that many.
scaleobject or nullA rating: 1–5 or 1–10 as numbers, stars or faces (stars and faces are 1–5).
4 fields in
scaleminintegermaxintegerOne of
5,10stylestringOne of
numbers,stars,emojilabelsstring[] or null
words_per_guestinteger1–3
resultsstringThe room sees the results as they come in, or once revealed.
One of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changebooleanstatestring requiredOne of
draft,live,closedrevealedbooleanrunintegerBumped by
reset: earlier votes stop counting.launched_atinteger or nullclosed_atinteger or nullcreated_atintegerupdated_atinteger
curl -X POST "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/polls" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
"The keynote",
"The workshop"
],
"results": "on_reveal"
}'{
"id": "pol_8Hk2mQ4xZ1",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"order": 0,
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
{
"id": "a1b2",
"label": "The keynote"
},
{
"id": "c3d4",
"label": "The workshop"
}
],
"max_choices": 1,
"scale": null,
"words_per_guest": 1,
"results": "on_reveal",
"phone_results": "after_vote",
"number_format": "percent",
"allow_change": true,
"state": "live",
"revealed": false,
"run": 1,
"launched_at": 1790150400000,
"closed_at": null,
"created_at": 1790140000000,
"updated_at": 1790150400000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Change a poll
/polls/{poll_id} engagements:writeChange its setup; what you leave out stays. A poll that has run keeps its type, its answers’ ids, its scale and word count until it is reset (409); its words may change.
Parameters
poll_idstring required pathThe poll id (from
GET /engagements/{id}/polls).
Request body required
typestringOne of
choice,word_cloud,ratingquestionstringUp to 200 characters
optionsstring or object[]max_choicesintegerscaleobjectwords_per_guestintegerresultsstringOne of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changeboolean
Responses
- 200 The poll.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstring requiredengagement_idstring requiredorderintegertypestring requiredOne of
choice,word_cloud,ratingquestionstring requiredUp to 200 characters
optionsobject[]Multiple choice: 2–10 answers. The id never changes when the label does.
2 fields in
optionsidstringlabelstringUp to 80 characters
max_choicesinteger1 = one answer; more = up to that many.
scaleobject or nullA rating: 1–5 or 1–10 as numbers, stars or faces (stars and faces are 1–5).
4 fields in
scaleminintegermaxintegerOne of
5,10stylestringOne of
numbers,stars,emojilabelsstring[] or null
words_per_guestinteger1–3
resultsstringThe room sees the results as they come in, or once revealed.
One of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changebooleanstatestring requiredOne of
draft,live,closedrevealedbooleanrunintegerBumped by
reset: earlier votes stop counting.launched_atinteger or nullclosed_atinteger or nullcreated_atintegerupdated_atinteger
curl -X PATCH "https://www.wunderframe.com/api/v1/polls/poll_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "Which session should run again on Friday?",
"results": "live"
}'{
"id": "pol_8Hk2mQ4xZ1",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"order": 0,
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
{
"id": "a1b2",
"label": "The keynote"
},
{
"id": "c3d4",
"label": "The workshop"
}
],
"max_choices": 1,
"scale": null,
"words_per_guest": 1,
"results": "on_reveal",
"phone_results": "after_vote",
"number_format": "percent",
"allow_change": true,
"state": "live",
"revealed": false,
"run": 1,
"launched_at": 1790150400000,
"closed_at": null,
"created_at": 1790140000000,
"updated_at": 1790150400000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Delete a poll
/polls/{poll_id} engagements:writeThe poll and every vote on it. The phones and screens let it go.
Parameters
poll_idstring required pathThe poll id (from
GET /engagements/{id}/polls).
Responses
- 204 Deleted.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
curl -X DELETE "https://www.wunderframe.com/api/v1/polls/poll_id" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"204 — no body.
Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Run a poll
/polls/{poll_id}/{action} engagements:writelaunch puts it on the phones and screens (closing the engagement’s live poll first); close stops the voting; reveal and hide show or hide the results of a poll whose results is on_reveal; reset starts a new run from zero votes.
Parameters
poll_idstring required pathThe poll id (from
GET /engagements/{id}/polls).actionstring required pathOne of
launch,close,reveal,hide,reset
Responses
- 200 The poll after the move.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstring requiredengagement_idstring requiredorderintegertypestring requiredOne of
choice,word_cloud,ratingquestionstring requiredUp to 200 characters
optionsobject[]Multiple choice: 2–10 answers. The id never changes when the label does.
2 fields in
optionsidstringlabelstringUp to 80 characters
max_choicesinteger1 = one answer; more = up to that many.
scaleobject or nullA rating: 1–5 or 1–10 as numbers, stars or faces (stars and faces are 1–5).
4 fields in
scaleminintegermaxintegerOne of
5,10stylestringOne of
numbers,stars,emojilabelsstring[] or null
words_per_guestinteger1–3
resultsstringThe room sees the results as they come in, or once revealed.
One of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changebooleanstatestring requiredOne of
draft,live,closedrevealedbooleanrunintegerBumped by
reset: earlier votes stop counting.launched_atinteger or nullclosed_atinteger or nullcreated_atintegerupdated_atinteger
curl -X POST "https://www.wunderframe.com/api/v1/polls/poll_id/action" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "pol_8Hk2mQ4xZ1",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"order": 0,
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
{
"id": "a1b2",
"label": "The keynote"
},
{
"id": "c3d4",
"label": "The workshop"
}
],
"max_choices": 1,
"scale": null,
"words_per_guest": 1,
"results": "on_reveal",
"phone_results": "after_vote",
"number_format": "percent",
"allow_change": true,
"state": "live",
"revealed": false,
"run": 1,
"launched_at": 1790150400000,
"closed_at": null,
"created_at": 1790140000000,
"updated_at": 1790150400000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Get a poll’s results
/polls/{poll_id}/results engagements:readThe current run’s tally — also while it is hidden from the room. ?format=csv answers a CSV file. Counts only: no participant ever appears.
Parameters
poll_idstring required pathThe poll id (from
GET /engagements/{id}/polls).formatstring queryOne of
json,csv
Responses
- 200 The poll and its tally (
nullbefore its first launch). - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
pollobject20 fields in
pollidstring requiredengagement_idstring requiredorderintegertypestring requiredOne of
choice,word_cloud,ratingquestionstring requiredUp to 200 characters
optionsobject[]Multiple choice: 2–10 answers. The id never changes when the label does.
2 fields in
optionsidstringlabelstringUp to 80 characters
max_choicesinteger1 = one answer; more = up to that many.
scaleobject or nullA rating: 1–5 or 1–10 as numbers, stars or faces (stars and faces are 1–5).
4 fields in
scaleminintegermaxintegerOne of
5,10stylestringOne of
numbers,stars,emojilabelsstring[] or null
words_per_guestinteger1–3
resultsstringThe room sees the results as they come in, or once revealed.
One of
live,on_revealphone_resultsstringOne of
after_vote,on_reveal,nevernumber_formatstringOne of
percent,count,bothallow_changebooleanstatestring requiredOne of
draft,live,closedrevealedbooleanrunintegerBumped by
reset: earlier votes stop counting.launched_atinteger or nullclosed_atinteger or nullcreated_atintegerupdated_atinteger
tallyobject or any or null
curl "https://www.wunderframe.com/api/v1/polls/poll_id/results" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"poll": {
"id": "pol_8Hk2mQ4xZ1",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"order": 0,
"type": "choice",
"question": "Which session should run again tomorrow?",
"options": [
{
"id": "a1b2",
"label": "The keynote"
},
{
"id": "c3d4",
"label": "The workshop"
}
],
"max_choices": 1,
"scale": null,
"words_per_guest": 1,
"results": "on_reveal",
"phone_results": "after_vote",
"number_format": "percent",
"allow_change": true,
"state": "live",
"revealed": false,
"run": 1,
"launched_at": 1790150400000,
"closed_at": null,
"created_at": 1790140000000,
"updated_at": 1790150400000
},
"tally": {
"poll_id": "pol_8Hk2mQ4xZ1",
"run": 1,
"voters": 312,
"options": [
{
"id": "a1b2",
"count": 201,
"pct": 64
},
{
"id": "c3d4",
"count": 111,
"pct": 36
}
],
"at": 1790150460000
}
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}List engagements
/engagements engagements:readThe workspace’s live engagements, newest first, with their state, what they take and their rooms.
Parameters
limitinteger queryPage size, 1–200.
1–200. Default
50cursorstring queryThe
next_cursorof the previous page.
Responses
- 200 A page of engagements.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
itemsobject[] required15 fields in
itemsidstring requirednamestring requiredkeystring requiredThe public key in the guests’ link,
/e/<key>.urlstring (uri) requiredThe phone page guests open.
statestring requiredopentakes posts;pausedtakes them but holds them off the live walls until it opens again;closedrefuses them (423).One of
open,paused,closedopenboolean requiredPosts are taken now (
openorpaused).moderationstring requiredopen: straight to the walls.approval: every post waits for the operator.auto_check: clean posts go through, flagged ones wait.One of
open,approval,auto_checkacceptsobject requiredWhat a post may be now, after the workspace’s rules (videos can be switched off for the whole workspace).
4 fields in
acceptsnotebooleanquestionbooleanphotobooleanvideoboolean
roomsobject[] requiredWhere posts can come from. Send a room’s
keyasroomon a post so walls set to that room show it.3 fields in
roomskeystring6 characters; never changes when the room is renamed.
labelstringurlstring (uri)
consentobject requiredWhen
required, show the text to the person in their language (texts.de/texts.en/texts.fr;textis the German) and sendconsent: truewith their post. The workspace default wording exists in all three; an own text is the same in each.3 fields in
consentrequiredbooleantextstringtextsobject3 fields in
textsdestringenstringfrstring
limitsobject requiredThe file sizes, video length and text lengths this engagement takes.
9 fields in
limitsphoto_max_bytesintegervideo_max_bytesintegervideo_max_sintegernote_max_charsintegerquestion_max_charsintegerphoto_caption_max_charsintegervideo_caption_max_charsintegername_max_charsintegerclient_label_max_charsinteger
scopeobjectWhat it belongs to: the workspace, a show or a session.
2 fields in
scopekindstringOne of
workspace,room,show,sessionidstring
created_atinteger or nullEpoch milliseconds.
availabilityobject or nullWhen guests can take part: why (
always,window= its opening hours,range= its dates,session,on_screen,manual,override,shot_setting), until when it stays as it is, when it opens next, and one sentence in German, English and French ("Guests can take part now · until 22:00").5 fields in
availabilityreasonstringuntilinteger or nullEpoch milliseconds.
next_openinteger or nullEpoch milliseconds.
sentenceobject3 fields in
sentencedestringenstringfrstring
time_zonestring or null
retention_daysintegerPosts and photos are deleted this many days after they are posted.
next_cursorstring or null requiredPass as
cursorfor the next page;nullon the last page.
curl "https://www.wunderframe.com/api/v1/engagements?limit=50" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"items": [
{
"id": "eng_Q2mK7vB3xYcZp4dW1",
"name": "Gala night",
"key": "a6ptt2gre6vu",
"url": "https://www.wunderframe.com/e/a6ptt2gre6vu",
"state": "open",
"open": true,
"moderation": "approval",
"accepts": {
"note": true,
"question": false,
"photo": true,
"video": true
},
"rooms": [
{
"key": "up82k8",
"label": "Lobby",
"url": "https://www.wunderframe.com/e/a6ptt2gre6vu/up82k8"
}
],
"consent": {
"required": true,
"text": "Ich bin einverstanden, dass mein Beitrag und mein Foto auf den Bildschirmen gezeigt und bis zum Ende der Aufbewahrungsfrist gespeichert werden.",
"texts": {
"de": "Ich bin einverstanden, dass mein Beitrag und mein Foto auf den Bildschirmen gezeigt und bis zum Ende der Aufbewahrungsfrist gespeichert werden.",
"en": "I agree that my post and photo are shown on the screens and kept until the retention period ends.",
"fr": "J’accepte que ma publication et ma photo soient affichées sur les écrans et conservées jusqu’à la fin de la durée de conservation."
}
},
"limits": {
"photo_max_bytes": 8388608,
"video_max_bytes": 62914560,
"video_max_s": 15,
"note_max_chars": 280,
"question_max_chars": 280,
"photo_caption_max_chars": 60,
"video_caption_max_chars": 60,
"name_max_chars": 40,
"client_label_max_chars": 40
},
"scope": {
"kind": "workspace",
"id": "ws_acme"
},
"created_at": 1790150400000
}
],
"next_cursor": null
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Get an engagement
/engagements/{id} engagements:readWhether it takes posts now (state), what kinds, its rooms, whether consent is asked and the limits.
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Responses
- 200 The engagement.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
idstring requirednamestring requiredkeystring requiredThe public key in the guests’ link,
/e/<key>.urlstring (uri) requiredThe phone page guests open.
statestring requiredopentakes posts;pausedtakes them but holds them off the live walls until it opens again;closedrefuses them (423).One of
open,paused,closedopenboolean requiredPosts are taken now (
openorpaused).moderationstring requiredopen: straight to the walls.approval: every post waits for the operator.auto_check: clean posts go through, flagged ones wait.One of
open,approval,auto_checkacceptsobject requiredWhat a post may be now, after the workspace’s rules (videos can be switched off for the whole workspace).
4 fields in
acceptsnotebooleanquestionbooleanphotobooleanvideoboolean
roomsobject[] requiredWhere posts can come from. Send a room’s
keyasroomon a post so walls set to that room show it.3 fields in
roomskeystring6 characters; never changes when the room is renamed.
labelstringurlstring (uri)
consentobject requiredWhen
required, show the text to the person in their language (texts.de/texts.en/texts.fr;textis the German) and sendconsent: truewith their post. The workspace default wording exists in all three; an own text is the same in each.3 fields in
consentrequiredbooleantextstringtextsobject3 fields in
textsdestringenstringfrstring
limitsobject requiredThe file sizes, video length and text lengths this engagement takes.
9 fields in
limitsphoto_max_bytesintegervideo_max_bytesintegervideo_max_sintegernote_max_charsintegerquestion_max_charsintegerphoto_caption_max_charsintegervideo_caption_max_charsintegername_max_charsintegerclient_label_max_charsinteger
scopeobjectWhat it belongs to: the workspace, a show or a session.
2 fields in
scopekindstringOne of
workspace,room,show,sessionidstring
created_atinteger or nullEpoch milliseconds.
availabilityobject or nullWhen guests can take part: why (
always,window= its opening hours,range= its dates,session,on_screen,manual,override,shot_setting), until when it stays as it is, when it opens next, and one sentence in German, English and French ("Guests can take part now · until 22:00").5 fields in
availabilityreasonstringuntilinteger or nullEpoch milliseconds.
next_openinteger or nullEpoch milliseconds.
sentenceobject3 fields in
sentencedestringenstringfrstring
time_zonestring or null
retention_daysintegerPosts and photos are deleted this many days after they are posted.
curl "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "eng_Q2mK7vB3xYcZp4dW1",
"name": "Gala night",
"key": "a6ptt2gre6vu",
"url": "https://www.wunderframe.com/e/a6ptt2gre6vu",
"state": "open",
"open": true,
"moderation": "approval",
"accepts": {
"note": true,
"question": false,
"photo": true,
"video": true
},
"rooms": [
{
"key": "up82k8",
"label": "Lobby",
"url": "https://www.wunderframe.com/e/a6ptt2gre6vu/up82k8"
}
],
"consent": {
"required": true,
"text": "Ich bin einverstanden, dass mein Beitrag und mein Foto auf den Bildschirmen gezeigt und bis zum Ende der Aufbewahrungsfrist gespeichert werden.",
"texts": {
"de": "Ich bin einverstanden, dass mein Beitrag und mein Foto auf den Bildschirmen gezeigt und bis zum Ende der Aufbewahrungsfrist gespeichert werden.",
"en": "I agree that my post and photo are shown on the screens and kept until the retention period ends.",
"fr": "J’accepte que ma publication et ma photo soient affichées sur les écrans et conservées jusqu’à la fin de la durée de conservation."
}
},
"limits": {
"photo_max_bytes": 8388608,
"video_max_bytes": 62914560,
"video_max_s": 15,
"note_max_chars": 280,
"question_max_chars": 280,
"photo_caption_max_chars": 60,
"video_caption_max_chars": 60,
"name_max_chars": 40,
"client_label_max_chars": 40
},
"scope": {
"kind": "workspace",
"id": "ws_acme"
},
"created_at": 1790150400000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Get an upload URL
/engagements/{id}/uploads engagements:writeStep 1 of a photo or video post: one signed URL for one file. PUT the file’s bytes to upload_url with headers, then name upload_id in POST /engagements/{id}/posts. The file is checked when you post: it must really be the type you named and no larger than bytes.
Parameters
idstring required pathThe engagement id (from
GET /engagements).
Request body required
kindstring requiredOne of
photo,videomimestring requiredA photo:
image/jpeg,image/pngorimage/webp. A video:video/mp4,video/quicktimeorvideo/webm.bytesinteger requiredThe file’s exact size. A photo up to 8 MB, a video up to 60 MB.
At least 1
Responses
- 201 Where to send the file.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 413 Too large.
- 415 Unsupported media type.
- 423 Closed: the engagement takes no posts right now.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After). - 503 A service this call needs is not available right now.
Response fields (201)
upload_idstring requiredName it in the post. Good for one post, within an hour.
upload_urlstring (uri) requiredSend the file’s bytes here, at once (the URL is short-lived and single-use).
methodstring requiredUsually
PUT.headersmap of string requiredSend these headers with the upload.
curl -X POST "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/uploads" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "photo",
"mime": "image/jpeg",
"bytes": 1843200
}'{
"upload_id": "u3f9a0c1d2e4b5a6978c0",
"upload_url": "https://www.wunderframe.com/_storage/upload/…",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
}
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Post like a guest
/engagements/{id}/posts engagements:writeA photo, video, note or question, through the same moderation as a guest’s post. It reaches the walls at once when moderation is open (a video once it is made small), or waits for the operator. While the engagement is paused, posts are taken but held off the live walls; while it is closed, 423. Send an Idempotency-Key header to retry safely: the same key and body answer the same post for 24 hours (200, Idempotent-Replayed: true).
Parameters
idstring required pathThe engagement id (from
GET /engagements).Idempotency-Keystring headerAny unique string (1–200 printable characters), e.g. a UUID per shot. A retry with the same key and body returns the first post; another body is a
409.Up to 200 characters
Request body required
kindstring requiredOne of
photo,video,note,questionupload_idstringA photo or video: the
upload_idits upload returned, after the PUT.captionstringA photo’s or video’s caption, or the words of a note or question (
textis the same field).textstringThe same as
caption.namestringThe person’s name, shown with the post. Leave it out for anonymous.
Up to 40 characters
roomstringA room key (or label) of the engagement. An unknown room is refused.
client_labelstringYour own label for where it came from, e.g.
Booth 1. Stored on the post.Up to 40 characters
consentbooleanRequired (
true) when the engagement’sconsent.required: the person agreed to the consent text.
Responses
- 200 A retry under the same
Idempotency-Key: the first post, as it is now. - 201 The post, with its take-home link.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 409 Conflicts with the current state.
- 413 Too large.
- 415 Unsupported media type.
- 423 Closed: the engagement takes no posts right now.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After). - 503 A service this call needs is not available right now.
Response fields (200)
post_idstring requiredengagement_idstring requiredkindstring requiredOne of
photo,video,note,questionstatusstring requiredpendingwaits for the operator;approved,featuredandansweredare on the walls;rejectedandhiddenare not.One of
pending,approved,featured,answered,rejected,hiddenvideo_statusstring or nullA video is made small on the server first; it reaches the walls once
ready.nullfor other kinds.One of
processing,ready,failed,nullroomstring or nullThe room key.
client_labelstring or nullshare_readyboolean requiredThe take-home copy is made: the take-home page shows it at once. On the free plan it carries the Wunderframe watermark.
take_home_urlstring (uri) or null requiredA signed link to a page where the person sees their photo or video and can share or save it. Show it as a QR code. For one post, for 24 hours after it was made.
nullfor notes and questions, and for a post that was rejected or taken down.take_home_expires_atinteger or nullWhen the link stops working (epoch milliseconds).
sourcestringWhere it came from: a guest’s phone, an API client, or a stage set to Photo booth.
One of
phone,api,boothcropobject or nullThe face-aware crop box every screen frames the photo by: 0..1 of the original, x and y the top-left corner.
nulluntil faces were looked for.4 fields in
cropxnumberynumberwnumberhnumber
crop_sourcestring or nullOne of
faces,booth,center,manual,nullkeepsake_readybooleanThe branded keepsake polaroid is made; the take-home page shows and shares it.
keepsake_urlstring (uri) or nullWhere the person gets the keepsake: the take-home page.
created_atinteger requiredEpoch milliseconds.
Response fields (201)
post_idstring requiredengagement_idstring requiredkindstring requiredOne of
photo,video,note,questionstatusstring requiredpendingwaits for the operator;approved,featuredandansweredare on the walls;rejectedandhiddenare not.One of
pending,approved,featured,answered,rejected,hiddenvideo_statusstring or nullA video is made small on the server first; it reaches the walls once
ready.nullfor other kinds.One of
processing,ready,failed,nullroomstring or nullThe room key.
client_labelstring or nullshare_readyboolean requiredThe take-home copy is made: the take-home page shows it at once. On the free plan it carries the Wunderframe watermark.
take_home_urlstring (uri) or null requiredA signed link to a page where the person sees their photo or video and can share or save it. Show it as a QR code. For one post, for 24 hours after it was made.
nullfor notes and questions, and for a post that was rejected or taken down.take_home_expires_atinteger or nullWhen the link stops working (epoch milliseconds).
sourcestringWhere it came from: a guest’s phone, an API client, or a stage set to Photo booth.
One of
phone,api,boothcropobject or nullThe face-aware crop box every screen frames the photo by: 0..1 of the original, x and y the top-left corner.
nulluntil faces were looked for.4 fields in
cropxnumberynumberwnumberhnumber
crop_sourcestring or nullOne of
faces,booth,center,manual,nullkeepsake_readybooleanThe branded keepsake polaroid is made; the take-home page shows and shares it.
keepsake_urlstring (uri) or nullWhere the person gets the keepsake: the take-home page.
created_atinteger requiredEpoch milliseconds.
curl -X POST "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/posts" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "photo",
"upload_id": "u3f9a0c1d2e4b5a6978c0",
"caption": "Gala night 2026",
"room": "up82k8",
"client_label": "Booth 1",
"consent": true
}'{
"post_id": "pst_7vB3xYcZp4dW1qR8",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"kind": "photo",
"status": "pending",
"video_status": null,
"room": "up82k8",
"client_label": "Booth 1",
"share_ready": false,
"take_home_url": "https://www.wunderframe.com/e/a6ptt2gre6vu/p/pst_7vB3xYcZp4dW1qR8?t=sp1x2c.hK3…",
"take_home_expires_at": 1790236800000,
"source": "api",
"crop": {
"x": 0.12,
"y": 0,
"w": 0.75,
"h": 1
},
"crop_source": "faces",
"keepsake_ready": false,
"keepsake_url": "https://www.wunderframe.com/e/a6ptt2gre6vu/p/pst_7vB3xYcZp4dW1qR8?t=sp1x2c.hK3…",
"created_at": 1790150400000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Get a post’s status
/engagements/{id}/posts/{post_id} engagements:writePoll this (every 2–3 seconds) until share_ready is true, then show take_home_url as a QR code. Only posts made with the same API key are found.
Parameters
idstring required pathThe engagement id (from
GET /engagements).post_idstring required pathThe post id
POST /engagements/{id}/postsreturned.
Responses
- 200 The post as it is now.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
- 429 Too many requests for this key: wait
details.retry_after_sseconds (also inRetry-After).
Response fields (200)
post_idstring requiredengagement_idstring requiredkindstring requiredOne of
photo,video,note,questionstatusstring requiredpendingwaits for the operator;approved,featuredandansweredare on the walls;rejectedandhiddenare not.One of
pending,approved,featured,answered,rejected,hiddenvideo_statusstring or nullA video is made small on the server first; it reaches the walls once
ready.nullfor other kinds.One of
processing,ready,failed,nullroomstring or nullThe room key.
client_labelstring or nullshare_readyboolean requiredThe take-home copy is made: the take-home page shows it at once. On the free plan it carries the Wunderframe watermark.
take_home_urlstring (uri) or null requiredA signed link to a page where the person sees their photo or video and can share or save it. Show it as a QR code. For one post, for 24 hours after it was made.
nullfor notes and questions, and for a post that was rejected or taken down.take_home_expires_atinteger or nullWhen the link stops working (epoch milliseconds).
sourcestringWhere it came from: a guest’s phone, an API client, or a stage set to Photo booth.
One of
phone,api,boothcropobject or nullThe face-aware crop box every screen frames the photo by: 0..1 of the original, x and y the top-left corner.
nulluntil faces were looked for.4 fields in
cropxnumberynumberwnumberhnumber
crop_sourcestring or nullOne of
faces,booth,center,manual,nullkeepsake_readybooleanThe branded keepsake polaroid is made; the take-home page shows and shares it.
keepsake_urlstring (uri) or nullWhere the person gets the keepsake: the take-home page.
created_atinteger requiredEpoch milliseconds.
curl "https://www.wunderframe.com/api/v1/engagements/eng_Q2mK7vB3xYcZp4dW1/posts/pst_7vB3xYcZp4dW1qR8" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"post_id": "pst_7vB3xYcZp4dW1qR8",
"engagement_id": "eng_Q2mK7vB3xYcZp4dW1",
"kind": "photo",
"status": "pending",
"video_status": null,
"room": "up82k8",
"client_label": "Booth 1",
"share_ready": false,
"take_home_url": "https://www.wunderframe.com/e/a6ptt2gre6vu/p/pst_7vB3xYcZp4dW1qR8?t=sp1x2c.hK3…",
"take_home_expires_at": 1790236800000,
"source": "api",
"crop": {
"x": 0.12,
"y": 0,
"w": 0.75,
"h": 1
},
"crop_source": "faces",
"keepsake_ready": false,
"keepsake_url": "https://www.wunderframe.com/e/a6ptt2gre6vu/p/pst_7vB3xYcZp4dW1qR8?t=sp1x2c.hK3…",
"created_at": 1790150400000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}