Join
Participant PIN lookup and join, called by the join page.
Look up a PIN
GET /v1/pins/{pin}
Credential: None · Rate limit: 1,000/min (burst 1,000) per IP; 20 PIN misses/min per IP (shared with join)
Lets the join page fail fast on a mistyped PIN and show the title before the nickname step.
A miss counts toward the join-miss limit. Send the PIN as exactly 6 digits. Give each attempt
5 s; after a timeout, or a 502 or 504 without an error body, retry once at once with
?r=1.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
pin required | path | string | Pattern ^[0-9]{6}$. Example 482913. |
Responses
200 A live session has this PIN.
| Field | Type | Description |
|---|---|---|
status required | live | |
presentation_title required | string | |
phone_display required | string | One of full, answers_only. |
accent_color required | string | null | Pattern ^#[0-9A-Fa-f]{6}$. |
participants_full required | boolean |
| Status | Error codes |
|---|---|
| 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. |
| 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). |
| 429 | rate_limited, with retry_after_s. |
| 500 | internal_error. |
| 503 | no_live_capacity or service_unavailable, with retry_after_s and Retry-After. Billing endpoints return service_unavailable when billing is not configured in the environment or Stripe is unreachable. |
Join a session
POST /v1/pins/{pin}/join
Credential: None, or Participant token (optional body field on re-join) · Rate limit: 1,000/min (burst 1,000) per IP; 20 PIN misses/min per IP
Joins with {nickname}, or re-joins with {participant_token} (re-joins never count
against the participant cap); the PIN is the path's. This is the only participant-cap
check. Questions are not returned; the live session pushes the current question over the
WebSocket. Body ≤ 16 KB. Send the PIN as exactly 6 digits. Give each attempt 5 s; after a
timeout, or a 502 or 504 without an error body, retry once at once with ?r=1 and the
same body. With the same join_id a retry returns the first attempt's participant and token
instead of admitting the phone twice.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
pin required | path | string | Pattern ^[0-9]{6}$. Example 482913. |
Request body
application/json
One of:
Variant 1
| Field | Type | Description |
|---|---|---|
nickname required | string | 1–20 characters after trim and NFC normalization; no control characters. at least 1 chars. |
join_id | string | Client join nonce: a fresh random value of at least 128 bits per join (the join
page sends 16 random bytes, base64url), reused unchanged on every retry of that
join. The same join_id with the same nickname in the same session returns the
same participant and token for 10 minutes; with another nickname,
409 idempotency_conflict. A secret: never log or share it. Pattern ^[A-Za-z0-9_-]{22,64}$. |
Variant 2
| Field | Type | Description |
|---|---|---|
participant_token required | string | Pattern ^ptk_[0-9A-Za-z]{32,}$. |
Responses
200 Joined.
| Field | Type | Description |
|---|---|---|
participant_id required | string | Pattern ^par_[0-9A-HJKMNP-TV-Z]{26}$. |
participant_token required | string | Pattern ^ptk_[0-9A-Za-z]{32,}$. |
nickname required | string | The accepted name, possibly with a suffix (Sam 2). |
rejoined required | boolean | |
session_id required | string | Pattern ^ses_[0-9A-HJKMNP-TV-Z]{26}$. Example ses_01K7Q4A1B2C3D4E5F6G7H8J9K0. |
live_url required | string | |
presentation required | object | |
presentation.title required | string | |
presentation.phone_display required | string | One of full, answers_only. |
presentation.accent_color required | string | null | Pattern ^#[0-9A-Fa-f]{6}$. |
session required | object | |
session.status required | live | |
session.participant_cap required | integer | At least 1. |
| Status | Error codes |
|---|---|
| 400 | invalid_json, validation_failed (see details), invalid_request. |
| 401 | invalid_credentials: the re-join participant_token is unknown or expired (for
example, a token stored by the browser from an earlier session). Join again with a
nickname. The message is the participant_token variant from errors.json
("This participant token is unknown or has expired. Join again with a nickname."). |
| 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. |
| 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. |
| 503 | no_live_capacity or service_unavailable, with retry_after_s and Retry-After. Billing endpoints return service_unavailable when billing is not configured in the environment or Stripe is unreachable. |