Presentations
Save, read, list and delete presentations (title, settings, questions).
- GET
/v1/presentations - POST
/v1/presentations - GET
/v1/presentations/{presentation_id} - PUT
/v1/presentations/{presentation_id} - DELETE
/v1/presentations/{presentation_id} - POST
/v1/validate - GET
/v1/question-types
List presentations
GET /v1/presentations
Credential: Secret key (Authorization: Bearer spk_live_…)
Newest first.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | 1–100. Default 20. |
cursor | query | string | Opaque next_cursor from the previous page; valid 24 h. |
Responses
200 One page of presentations.
| Field | Type | Description |
|---|---|---|
data required | array of object | |
data[].presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
data[].title required | string | |
data[].version required | integer | At least 1. |
data[].question_count required | integer | At least 1. |
data[].live_session_id required | string | null | |
data[].created_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
data[].updated_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
next_cursor required | string | null |
| Status | Error codes |
|---|---|
| 400 | invalid_json, validation_failed (see details), invalid_request. |
| 401 | missing_credentials, invalid_credentials, not_signed_in (/v1/web/*). |
| 403 | wrong_credential_type, secret_key_in_browser, csrf_failed, recent_auth_required, session_full, nickname_rejected. |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
Create a presentation
POST /v1/presentations
Credential: Secret key (Authorization: Bearer spk_live_…) · Rate limit: 20 req/s per key, burst 40
Saves a new presentation. warnings never block a save.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string | The first response is stored 24 h per (account, key) and replayed verbatim. Same key with a different body → 409 idempotency_conflict. 1–64 chars. |
Request body
application/json
| Field | Type | Description |
|---|---|---|
title required | string | Presentation title. 1–120 chars. |
settings | Settings | Optional. Omitted fields take their defaults. |
questions required | array of Question | 1-50 questions in suggested slide order. Each id must be unique. 1–50 items. |
{
"title": "Q3 All-Hands",
"settings": {
"phone_display": "full",
"leaderboard_size": 5,
"show_rank_on_phone": true
},
"questions": [
{
"id": "warmup",
"type": "word_cloud",
"prompt": "One word for this quarter?"
},
{
"id": "priority",
"type": "poll",
"prompt": "What should we focus on in Q4?",
"options": [
"Performance",
"New features",
"Onboarding",
"Docs"
]
},
{
"id": "launch_year",
"type": "quiz",
"prompt": "What year did we ship 1.0?",
"options": [
"2021",
"2022",
"2023",
"2024"
],
"correct": 1,
"explanation": "Version 1.0 shipped in March 2022.",
"time_limit_s": 20
}
]
}
Responses
201 Created.
| Field | Type | Description |
|---|---|---|
presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
version required | integer | At least 1. |
title required | string | |
question_ids required | array of string | |
public_url required | string | Format uri. |
sessions_url required | string | Format uri. |
created_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
updated_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
live_session | object | Present on replace when a session is live. |
live_session.session_id required | string | Pattern ^ses_[0-9A-HJKMNP-TV-Z]{26}$. Example ses_01K7Q4A1B2C3D4E5F6G7H8J9K0. |
live_session.uses_version required | integer | At least 1. |
warnings required | array of object | |
warnings[].code required | string | Presentation saves and POST /v1/validate: defaulted, long_prompt, few_options,
speed_bonus_unused, ids_removed, live_session_uses_previous_version.
GET /v1/account: payment_past_due (Pro stays active while Stripe retries) and
secret_key_in_browser (a key was sent from a browser and refused). Clients must
accept codes not listed here. One of defaulted, long_prompt, few_options, speed_bonus_unused, ids_removed, live_session_uses_previous_version, payment_past_due, secret_key_in_browser. |
warnings[].path | string | |
warnings[].message required | string |
| Status | Error codes |
|---|---|
| 400 | invalid_json, validation_failed (see details), invalid_request. |
| 401 | missing_credentials, invalid_credentials, not_signed_in (/v1/web/*). |
| 402 | quota_exceeded with limit, used, max, resets_at, upgrade_url. |
| 403 | wrong_credential_type, secret_key_in_browser, csrf_failed, recent_auth_required, session_full, nickname_rejected. |
| 409 | session_ended, idempotency_conflict (same key, different body; or, retryable, the first request with that key is still in progress — retry the same request in a few seconds). |
| 413 | payload_too_large: body over the limit (256 KB; 16 KB for POST /v1/pins/{pin}/join). |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
Read a presentation
GET /v1/presentations/{presentation_id}
Credential: None: the presentation ID in the path is the capability, or Secret key (Authorization: Bearer spk_live_…) · Rate limit: 120/min per IP (public view)
One path, two views. Without Authorization the presentation ID itself is the capability
and the public view is returned (no secret fields such as correct, explanation; no
owner metadata). With the owner's secret key the owner view is returned. A non-owner
key gets 404 presentation_not_found. All defaults are materialized.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
presentation_id required | path | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
If-None-Match | header | string | An ETag from a previous response ("<version>"). |
Responses
200 The presentation. view tells which shape was returned.
One of:
PresentationPublicView
| Field | Type | Description |
|---|---|---|
view required | public | |
presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
version required | integer | At least 1. |
title required | string | |
settings required | Settings | Presentation-level settings. Every field is optional and has a default. |
questions required | array of object | |
questions[].id required | string | Pattern ^[a-z][a-z0-9_-]{0,31}$. |
questions[].type required | string | |
questions[].prompt required | string | |
updated_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
PresentationOwnerView
| Field | Type | Description |
|---|---|---|
view required | owner | |
presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
version required | integer | At least 1. |
title required | string | |
settings required | Settings | Presentation-level settings. Every field is optional and has a default. |
questions required | array of Question | |
created_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
updated_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
live_session required | object | null | |
stats required | object | |
stats.sessions_total required | integer | At least 0. |
stats.last_session_at required | string | null | Format date-time. |
304 Not modified (If-None-Match matched the current version).
| Status | Error codes |
|---|---|
| 401 | missing_credentials, invalid_credentials, not_signed_in (/v1/web/*). |
| 403 | wrong_credential_type, secret_key_in_browser, csrf_failed, recent_auth_required, session_full, nickname_rejected. |
| 404 | presentation_not_found, session_not_found, pin_not_found, question_not_found, key_not_found, grant_not_found, oauth_request_not_found, route_not_found. Ownership failures are always 404. |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
Replace a presentation
PUT /v1/presentations/{presentation_id}
Credential: Secret key (Authorization: Bearer spk_live_…)
Full replace (no partial patch; omitted settings fields reset to defaults). Increments
version. A live session keeps the snapshot it started with; the response then includes
live_session and warning live_session_uses_previous_version. Keep question IDs stable
so decks keep working.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
presentation_id required | path | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
If-Match | header | string | "<version>"; stale → 412 version_conflict. |
Request body
application/json
| Field | Type | Description |
|---|---|---|
title required | string | Presentation title. 1–120 chars. |
settings | Settings | Optional. Omitted fields take their defaults. |
questions required | array of Question | 1-50 questions in suggested slide order. Each id must be unique. 1–50 items. |
Responses
200 Replaced.
| Field | Type | Description |
|---|---|---|
presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
version required | integer | At least 1. |
title required | string | |
question_ids required | array of string | |
public_url required | string | Format uri. |
sessions_url required | string | Format uri. |
created_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
updated_at required | string | Format date-time. Example 2026-10-11T18:04:05Z. |
live_session | object | Present on replace when a session is live. |
live_session.session_id required | string | Pattern ^ses_[0-9A-HJKMNP-TV-Z]{26}$. Example ses_01K7Q4A1B2C3D4E5F6G7H8J9K0. |
live_session.uses_version required | integer | At least 1. |
warnings required | array of object | |
warnings[].code required | string | Presentation saves and POST /v1/validate: defaulted, long_prompt, few_options,
speed_bonus_unused, ids_removed, live_session_uses_previous_version.
GET /v1/account: payment_past_due (Pro stays active while Stripe retries) and
secret_key_in_browser (a key was sent from a browser and refused). Clients must
accept codes not listed here. One of defaulted, long_prompt, few_options, speed_bonus_unused, ids_removed, live_session_uses_previous_version, payment_past_due, secret_key_in_browser. |
warnings[].path | string | |
warnings[].message required | string |
| Status | Error codes |
|---|---|
| 400 | invalid_json, validation_failed (see details), invalid_request. |
| 401 | missing_credentials, invalid_credentials, not_signed_in (/v1/web/*). |
| 403 | wrong_credential_type, secret_key_in_browser, csrf_failed, recent_auth_required, session_full, nickname_rejected. |
| 404 | presentation_not_found, session_not_found, pin_not_found, question_not_found, key_not_found, grant_not_found, oauth_request_not_found, route_not_found. Ownership failures are always 404. |
| 412 | version_conflict: the If-Match version is stale. |
| 413 | payload_too_large: body over the limit (256 KB; 16 KB for POST /v1/pins/{pin}/join). |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
Delete a presentation
DELETE /v1/presentations/{presentation_id}
Credential: Secret key (Authorization: Bearer spk_live_…)
Ends the live session if any, then deletes the presentation. The ID is never reused. Past
sessions and their results stay readable. A repeat delete returns 404.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
presentation_id required | path | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
Responses
200 Deleted.
| Field | Type | Description |
|---|---|---|
deleted required | true | |
presentation_id required | string | Pattern ^pr_[0-9A-HJKMNP-TV-Z]{26}$. Example pr_01K7Q3W8Z9G5H2M4N6P8R0S2T4. |
ended_session_id required | string | null |
| Status | Error codes |
|---|---|
| 401 | missing_credentials, invalid_credentials, not_signed_in (/v1/web/*). |
| 403 | wrong_credential_type, secret_key_in_browser, csrf_failed, recent_auth_required, session_full, nickname_rejected. |
| 404 | presentation_not_found, session_not_found, pin_not_found, question_not_found, key_not_found, grant_not_found, oauth_request_not_found, route_not_found. Ownership failures are always 404. |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
Validate without saving
POST /v1/validate
Credential: None · Rate limit: 60/min per IP
Runs the save-time validator on a full presentation or on {"question": {…}}. Nothing is
stored. Always 200 when the body is parseable JSON; problems are reported in errors.
Request body
application/json
One of:
Presentation
| Field | Type | Description |
|---|---|---|
title required | string | Presentation title. 1–120 chars. |
settings | Settings | Optional. Omitted fields take their defaults. |
questions required | array of Question | 1-50 questions in suggested slide order. Each id must be unique. 1–50 items. |
Variant 2
| Field | Type | Description |
|---|---|---|
question required | Question | One question of any available type, selected by its type field. |
Responses
200 Validation result.
| Field | Type | Description |
|---|---|---|
valid required | boolean | |
errors required | array of object | |
errors[].path required | string | Example questions[2].correct. |
errors[].question_id | string | Pattern ^[a-z][a-z0-9_-]{0,31}$. |
errors[].code required | string | Example out_of_range. |
errors[].message required | string | |
warnings required | array of object | |
warnings[].code required | string | Presentation saves and POST /v1/validate: defaulted, long_prompt, few_options,
speed_bonus_unused, ids_removed, live_session_uses_previous_version.
GET /v1/account: payment_past_due (Pro stays active while Stripe retries) and
secret_key_in_browser (a key was sent from a browser and refused). Clients must
accept codes not listed here. One of defaulted, long_prompt, few_options, speed_bonus_unused, ids_removed, live_session_uses_previous_version, payment_past_due, secret_key_in_browser. |
warnings[].path | string | |
warnings[].message required | string | |
normalized required | object | null | When valid, the input with all defaults filled in (what would be stored); otherwise null. |
| Status | Error codes |
|---|---|
| 400 | invalid_json, validation_failed (see details), invalid_request. |
| 413 | payload_too_large: body over the limit (256 KB; 16 KB for POST /v1/pins/{pin}/join). |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
List question types and their schemas
GET /v1/question-types
Credential: None
Generated from the validator, so it cannot drift. Includes planned types with status: planned.
Responses
200 Question types.
| Field | Type | Description |
|---|---|---|
id_pattern required | ^[a-z][a-z0-9_-]{0,31}$ | |
presentation_schema required | object | JSON Schema (draft 2020-12) of the presentation object. |
types required | array of object | |
types[].type required | string | Example poll. |
types[].since required | string | Example 2026-10. |
types[].status required | string | One of available, planned. |
types[].description required | string | |
types[].schema required | object | null | JSON Schema (draft 2020-12) of the owner shape; null for planned types without a schema yet. |
types[].secret_fields required | array of string |
| Status | Error codes |
|---|---|
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |