API Documentation
Everything you need to integrate with PointPoker programmatically.
Table of Contents
Overview
The PointPoker API lets you create voting rooms, manage stories, and build integrations with your existing tools. It consists of a REST API for room management and Socket.io for real-time voting events.
Base URL
https://api.pointpoker.coDeep Links Base URL
https://pointpoker.co/voteAll REST responses use application/json. Errors return { "error": "message" } with an appropriate HTTP status code.
Authentication
No authentication is required for basic API usage. You can create rooms, join them, and participate in voting without any API key or token.
Pro features (room passwords, custom card decks, and themes) require a proToken in the request body. This is an HMAC-SHA256 signed token obtained through the PointPoker Pro subscription.
/api/roomsCreate a new voting room. Returns the room ID, a shareable room code, and a join URL. The room expires after 72 hours of inactivity.
Rate limit: 10 requests per 15 minutes per IP
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| facilitatorName | string | Yes | Display name of the facilitator (max 30 chars) |
| cardScale | "fibonacci" | "tshirt" | "simple" | "custom" | Yes | Voting scale to use |
| roles | string[] | Yes | Participant roles (1-4 items, each 1-20 chars) |
| anonymousVoting | boolean | Yes | Whether votes are anonymous until reveal |
| excludedFromEstimate | string[] | No | Roles to exclude from estimate calculations (must be subset of roles) |
| autoReveal | boolean | No | Auto-reveal votes when all participants have voted (default: false) |
| votingTimerSeconds | number | No | Countdown timer per story in seconds (15-300, omit for no timer) |
| facilitatorParticipates | boolean | No | Whether the facilitator can vote (default: false) |
| storyQueue | QueuedStory[] | No | Pre-loaded stories to vote on (max 50 items). Each item: { title: string (1-200), description?: string (max 1000), url?: string (max 500) } |
| customCardValues | string[] | No | Custom card values when cardScale is "custom" (2-15 items, each 1-10 chars). Pro only. |
| theme | "default" | "midnight" | "retro" | "neon" | No | Visual theme for the room. Pro only. |
| password | string | No | Room password (max 50 chars). Pro only. |
| proToken | string | No | HMAC-SHA256 signed token for Pro feature verification |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | Unique room identifier (UUID) |
| roomCode | string | Yes | Short shareable room code |
| joinUrl | string | Yes | Full URL to join the room |
Example Response
{
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"roomCode": "ABC123",
"joinUrl": "https://pointpoker.co/room/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}curl
curl -X POST https://api.pointpoker.co/api/rooms \
-H "Content-Type: application/json" \
-d '{
"facilitatorName": "Sarah",
"cardScale": "fibonacci",
"roles": ["Dev", "QE"],
"anonymousVoting": false
}'JavaScript
const response = await fetch("https://api.pointpoker.co/api/rooms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
facilitatorName: "Sarah",
cardScale: "fibonacci",
roles: ["Dev", "QE"],
anonymousVoting: false,
}),
});
const { roomId, roomCode, joinUrl } = await response.json();Pro features (password, customCardValues, theme) require a valid proToken or will return 403.
The room code is a short alphanumeric string that participants can enter to join.
/api/rooms/code/:codeResolve a room code to its room ID. Used when participants join via room code instead of direct link.
Rate limit: 30 requests per minute per IP
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | The room code to resolve |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID for the given code |
Example Response
{
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}curl
curl https://api.pointpoker.co/api/rooms/code/ABC123JavaScript
const response = await fetch("https://api.pointpoker.co/api/rooms/code/ABC123");
const { roomId } = await response.json();Returns 404 if the room code does not exist or the room has expired.
/api/rooms/:id/infoGet public metadata about a room. Used to display the join form with available roles and current participant count.
Rate limit: 30 requests per minute per IP
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | The room ID |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | Room identifier |
| roomCode | string | Yes | Room code for sharing |
| roles | string[] | Yes | Available roles in this room |
| cardScale | string | Yes | Voting scale used in this room |
| participantCount | number | Yes | Current number of participants |
| maxParticipants | number | Yes | Maximum allowed participants (25) |
| passwordRequired | boolean | No | Whether a password is needed to join |
Example Response
{
"roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"roomCode": "ABC123",
"roles": ["Dev", "QE"],
"cardScale": "fibonacci",
"participantCount": 4,
"maxParticipants": 25,
"passwordRequired": false
}curl
curl https://api.pointpoker.co/api/rooms/a1b2c3d4-e5f6-7890-abcd-ef1234567890/infoJavaScript
const response = await fetch(
"https://api.pointpoker.co/api/rooms/a1b2c3d4-e5f6-7890-abcd-ef1234567890/info"
);
const roomInfo = await response.json();/healthHealth check endpoint. Returns server status, uptime in seconds, and current timestamp.
Response
| Name | Type | Required | Description |
|---|---|---|---|
| status | "ok" | Yes | Server status |
| uptime | number | Yes | Server uptime in seconds |
| timestamp | string | Yes | ISO 8601 timestamp |
Example Response
{
"status": "ok",
"uptime": 86400,
"timestamp": "2026-03-08T12:00:00.000Z"
}curl
curl https://api.pointpoker.co/healthJavaScript
const response = await fetch("https://api.pointpoker.co/health");
const { status, uptime } = await response.json();Platform Integration API
The Platform Integration API enables Slack, Microsoft Teams, and Discord bots to participate in PointPoker sessions via REST endpoints. This allows platform users to vote, reveal, and manage sessions without leaving their messaging app.
Feature flag: These endpoints require NEXT_PUBLIC_FEATURE_PLATFORM_INTEGRATIONS to be enabled. When disabled, all platform endpoints return 404.
Base Path
/api/platform/:roomIdAuthentication Model
Platform endpoints use a three-tier token model. Each token type has different permissions and is scoped to a specific room.
| Token | Obtained From | Permissions |
|---|---|---|
| apiToken | Pro subscription dashboard | Join rooms on behalf of platform users |
| facilitatorRestToken | Room creation response (facilitator only) | Reveal votes, end session, view status |
| participantToken | Join endpoint response | Submit votes, view status |
Pass tokens via the Authorization header: Bearer <token>
/api/platform/:roomId/joinJoin a room on behalf of a platform user (Slack, Teams, Discord). Creates a participant entry and returns tokens for subsequent actions.
Auth: Bearer apiToken
Rate limit: 10 requests per minute per API token
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID to join |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name of the participant (max 30 chars) |
| role | string | Yes | Participant role (must be in room's role list) |
| platformUserId | string | Yes | Unique user ID from the platform (e.g., Slack user ID) |
| platform | "slack" | "teams" | "discord" | Yes | Platform identifier |
| asObserver | boolean | No | Join as non-voting observer (default: false) |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| participantId | string | Yes | Unique participant identifier |
| participantToken | string | Yes | Token for vote/status actions |
| roomCode | string | Yes | Room code for sharing |
Example Response
{
"participantId": "p_abc123",
"participantToken": "pt_xxxxxxxxxxxxxxxx",
"roomCode": "ABC123"
}curl
curl -X POST https://api.pointpoker.co/api/platform/a1b2c3d4-.../join \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"name": "Sarah",
"role": "Dev",
"platformUserId": "U024BE7LH",
"platform": "slack"
}'JavaScript
const response = await fetch(
"https://api.pointpoker.co/api/platform/a1b2c3d4-.../join",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_TOKEN",
},
body: JSON.stringify({
name: "Sarah",
role: "Dev",
platformUserId: "U024BE7LH",
platform: "slack",
}),
}
);
const { participantId, participantToken } = await response.json();The participantToken is scoped to this participant and room. Use it for vote and status requests.
If the platformUserId already has an active session in this room, the existing participant is returned.
/api/platform/:roomId/voteSubmit a vote on behalf of a platform participant. The vote value must be valid for the room's card scale.
Auth: Bearer participantToken
Rate limit: 20 requests per minute per participant token
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| value | string | Yes | Card value to vote (must be valid for room's scale) |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| success | boolean | Yes | Whether the vote was accepted |
| voteCount | number | Yes | Total votes cast so far |
| totalEligible | number | Yes | Total eligible voters |
Example Response
{
"success": true,
"voteCount": 4,
"totalEligible": 6
}curl
curl -X POST https://api.pointpoker.co/api/platform/a1b2c3d4-.../vote \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_PARTICIPANT_TOKEN" \
-d '{ "value": "5" }'JavaScript
const response = await fetch(
"https://api.pointpoker.co/api/platform/a1b2c3d4-.../vote",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_PARTICIPANT_TOKEN",
},
body: JSON.stringify({ value: "5" }),
}
);
const { success, voteCount, totalEligible } = await response.json();Returns 400 if the value is not in the room's card scale.
Returns 409 if voting is not currently active (no story set or votes already revealed).
/api/platform/:roomId/revealReveal all votes for the current story. Requires facilitator-level authorization.
Auth: Bearer facilitatorRestToken
Rate limit: 5 requests per minute per facilitator token
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| votes | VoteResult[] | Yes | Array of participant votes with names and values |
| overall | OverallResult | Yes | Aggregate stats: average, median, consensus |
| byRole | RoleResult[] | Yes | Per-role breakdown of votes |
Example Response
{
"votes": [
{ "participantName": "Sarah", "participantRole": "Dev", "value": "5" },
{ "participantName": "Mike", "participantRole": "QE", "value": "8" }
],
"overall": { "average": 6.5, "median": 6.5, "consensus": "discuss" },
"byRole": [
{ "role": "Dev", "count": 1, "votes": ["5"], "average": 5, "median": 5 },
{ "role": "QE", "count": 1, "votes": ["8"], "average": 8, "median": 8 }
]
}curl
curl -X POST https://api.pointpoker.co/api/platform/a1b2c3d4-.../reveal \
-H "Authorization: Bearer YOUR_FACILITATOR_TOKEN"JavaScript
const response = await fetch(
"https://api.pointpoker.co/api/platform/a1b2c3d4-.../reveal",
{
method: "POST",
headers: { Authorization: "Bearer YOUR_FACILITATOR_TOKEN" },
}
);
const { votes, overall, byRole } = await response.json();Only the room facilitator can reveal votes. Returns 403 for non-facilitator tokens.
If autoReveal is enabled, votes are revealed automatically when all eligible voters have voted.
/api/platform/:roomId/statusGet the current voting status of a room, including the active story, vote counts, and whether votes have been revealed.
Auth: Bearer participantToken | facilitatorRestToken
Rate limit: 30 requests per minute per token
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| currentStory | Story | null | Yes | Currently active story or null |
| phase | "waiting" | "voting" | "revealed" | Yes | Current voting phase |
| voteCount | number | Yes | Number of votes cast |
| totalEligible | number | Yes | Total eligible voters |
| participantCount | number | Yes | Total participants in room |
| revealResult | RevealResult | null | No | Vote results (only when phase is "revealed") |
Example Response
{
"currentStory": { "id": "s_123", "title": "PP-42 Login flow", "description": null, "url": null },
"phase": "voting",
"voteCount": 3,
"totalEligible": 5,
"participantCount": 6,
"revealResult": null
}curl
curl https://api.pointpoker.co/api/platform/a1b2c3d4-.../status \
-H "Authorization: Bearer YOUR_PARTICIPANT_TOKEN"JavaScript
const response = await fetch(
"https://api.pointpoker.co/api/platform/a1b2c3d4-.../status",
{ headers: { Authorization: "Bearer YOUR_PARTICIPANT_TOKEN" } }
);
const status = await response.json();/api/platform/:roomId/end-sessionEnd the voting session and retrieve the full session history. Requires facilitator-level authorization.
Auth: Bearer facilitatorRestToken
Rate limit: 2 requests per minute per facilitator token
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| roomId | string | Yes | The room ID |
Response
| Name | Type | Required | Description |
|---|---|---|---|
| history | CompletedStory[] | Yes | Full voting history for all stories in the session |
| totalStories | number | Yes | Number of stories voted on |
| sessionDuration | number | Yes | Session duration in seconds |
Example Response
{
"history": [
{
"id": "s_123",
"title": "PP-42 Login flow",
"finalEstimate": "5",
"wasRevoted": false,
"result": { "overall": { "average": 5, "median": 5, "consensus": "consensus" } },
"completedAt": "2026-03-20T10:30:00.000Z"
}
],
"totalStories": 1,
"sessionDuration": 1800
}curl
curl -X POST https://api.pointpoker.co/api/platform/a1b2c3d4-.../end-session \
-H "Authorization: Bearer YOUR_FACILITATOR_TOKEN"JavaScript
const response = await fetch(
"https://api.pointpoker.co/api/platform/a1b2c3d4-.../end-session",
{
method: "POST",
headers: { Authorization: "Bearer YOUR_FACILITATOR_TOKEN" },
}
);
const { history, totalStories, sessionDuration } = await response.json();This action is irreversible. The room will be closed and all participants disconnected.
The history array includes all stories, even those that were revoted.
Deep Links
Deep links let you send users directly into a voting session with a story pre-loaded. The user enters their name, picks a card scale, and starts voting immediately.
URL Format
https://pointpoker.co/vote?title=PP-123&desc=User+story&url=https://...Query Parameters
| Param | Required | Description | Example |
|---|---|---|---|
| title | Yes | Story title to vote on (max 200) | PP-123 User login flow |
| desc | No | Story description or acceptance criteria (max 1000) | As a user, I want to... |
| url | No | Link back to the ticket (must start with http:// or https://) (max 500) | https://jira.example.com/browse/PP-123 |
| scale | No | Card scale to use (default: fibonacci) | tshirt |
| timer | No | Voting timer in seconds (15-300) | 60 |
| source | No | Source identifier for tracking (e.g., jira, github, slack) (max 20) | jira |
Example
https://pointpoker.co/vote?title=PP-123%20User%20login%20flow&desc=As%20a%20user%2C%20I%20want%20to%20log%20in&url=https%3A%2F%2Fjira.example.com%2Fbrowse%2FPP-123&scale=fibonacci&timer=60&source=jiraSocket.io Events
PointPoker uses Socket.io for real-time communication. Connect to the server and emit/listen for events to participate in voting sessions.
Connection
import { io } from "socket.io-client";
const socket = io("https://api.pointpoker.co", {
transports: ["websocket"],
});
// Join a room
socket.emit("join-room", {
roomId: "a1b2c3d4-...",
name: "Sarah",
role: "Dev",
}, (response) => {
if (response.success) {
console.log("Joined!", response.state);
} else {
console.error(response.error);
}
});Client → Server
Events you emit to the server.
join-roomJoin a voting room. Returns full room state on success.
{
roomId: string; // Room ID to join
name: string; // Your display name
role: string; // Your role (must be in room's role list)
asObserver?: boolean; // Join as non-voting observer
reconnectToken?: string; // Token for reconnecting after disconnect
avatarSeed?: string; // Seed for avatar generation
password?: string; // Room password (if required)
}Returns a callback with { success, state, participantId, reconnectToken } or { success: false, error }.
submit-voteSubmit or change your vote for the current story.
{ value: string } // Must be a valid card value for the room's scaleIf autoReveal is enabled and all eligible voters have voted, votes are automatically revealed.
clear-voteClear your current vote.
set-storyFacilitator onlySet the current story for voting. Clears all votes and starts a new round.
{
title: string; // Story title (required)
description?: string; // Story description (max 1000 chars)
url?: string; // Link to ticket (max 500 chars)
}reveal-votesFacilitator onlyReveal all votes and compute statistics (average, median, consensus).
When the "open controls" feature is enabled, any non-observer participant can reveal.
next-storyFacilitator onlyMove to the next story. Archives the current story to session history.
revoteFacilitator onlyClear all votes and restart voting on the current story.
add-to-queueFacilitator onlyAdd a story to the voting queue.
{
title: string; // Story title (1-200 chars)
description?: string; // Description (max 1000 chars)
url?: string; // Ticket URL (max 500 chars)
}bulk-add-to-queueFacilitator onlyAdd multiple stories to the queue at once.
{ titles: string[] } // Array of story titles (max 50 items)start-next-from-queueFacilitator onlyStart voting on the next story in the queue.
end-sessionFacilitator onlyEnd the voting session. Broadcasts session history to all participants and closes the room.
Server → Client
Events you listen for from the server.
room-stateFull room state broadcast. Sent on join and after major state changes.
{
config: RoomConfig;
participants: Participant[];
currentStory: Story | null;
votingStatus: Record<string, boolean>;
revealResult: RevealResult | null;
history: CompletedStory[];
storyQueue: QueuedStory[];
finalEstimateOverride: string | null;
}participant-joinedA new participant joined the room.
{
id: string;
name: string;
role: string;
isFacilitator: boolean;
isObserver: boolean;
connectionStatus: "online" | "offline";
hasVoted: boolean;
}vote-submittedA participant submitted or changed their vote (value is hidden until reveal).
{ participantId: string }votes-revealedVotes have been revealed. Includes per-vote data, averages, and consensus status.
{
votes: Array<{
participantId: string;
participantName: string;
participantRole: string;
value: string;
}>;
overall: {
average: number | null;
median: number | null;
consensus: "consensus" | "discuss";
};
byRole: Array<{
role: string;
count: number;
votes: string[];
average: number | null;
median: number | null;
}>;
hasRoleGap: boolean;
}story-changedThe current story has changed.
{
id: string;
title: string;
description: string | null;
url: string | null;
phase: "waiting" | "voting" | "revealed";
timerEndsAt: string | null;
}queue-updatedThe story queue has been modified (added, removed, or reordered).
{
queue: Array<{
id: string;
title: string;
description: string | null;
url: string | null;
}>
}session-endedThe session has ended. Includes full voting history for all stories.
{
history: Array<{
id: string;
title: string;
finalEstimate: string | null;
wasRevoted: boolean;
result: RevealResult;
completedAt: string;
}>
}errorAn error occurred processing your request.
{ message: string }Rate Limits
Rate limits protect the service from abuse. REST endpoints return standard RateLimit-* headers (draft-7). Socket events that exceed limits emit an error event.
REST API
| Scope | Limit | Window |
|---|---|---|
| All /api/* endpoints | 100 requests | 1 minute per IP |
| POST /api/rooms | 10 requests | 15 minutes per IP |
| GET /api/rooms/code/:code | 30 requests | 1 minute per IP |
| GET /api/rooms/:id/info | 30 requests | 1 minute per IP |
Socket.io Events
| Scope | Limit | Window |
|---|---|---|
| Socket connections | 20 connections | 1 minute per IP |
| join-room | 3 events | 30 seconds per socket |
| submit-vote / clear-vote | 10 events | 10 seconds per socket |
| set-story | 5 events | 30 seconds per socket |
| reveal-votes / next-story / revote | 3 events | 30 seconds per socket |
| add-to-queue / remove-from-queue / update-queue-item | 10 events | 30 seconds per socket |
| reorder-queue | 5 events | 10 seconds per socket |
| end-session | 2 events | 60 seconds per socket |
| Password attempts | 5 attempts | 15 minutes per IP:room |
Platform API
| Scope | Limit | Window |
|---|---|---|
| POST .../join | 10 requests | 1 minute per API token |
| POST .../vote | 20 requests | 1 minute per participant token |
| POST .../reveal | 5 requests | 1 minute per facilitator token |
| GET .../status | 30 requests | 1 minute per token |
| POST .../end-session | 2 requests | 1 minute per facilitator token |
Card Scales
Available card scales and their values. Use the scale ID when creating a room.
| ID | Name | Values |
|---|---|---|
| fibonacci | Fibonacci | 0, 1, 2, 3, 5, 8, 13, 21, ☕, ❓ |
| tshirt | T-Shirt | XS, S, M, L, XL, ☕, ❓ |
| simple | Simple (1-10) | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ☕, ❓ |
| custom | Custom (Pro) | 2-15 user-defined values |
Error Handling
All errors follow a consistent format.
HTTP Errors
{
"error": "Human-readable error message"
}| Status | Meaning |
|---|---|
| 400 | Invalid request body or parameters |
| 403 | Pro feature used without valid Pro token |
| 404 | Room or resource not found |
| 429 | Rate limit exceeded |
Socket.io Errors
Socket errors are emitted via the error event with a { message: string } payload. Common errors include invalid permissions (non-facilitator attempting facilitator actions), rate limiting, and room state violations (e.g., voting when no story is set).
Need help? Open an issue on GitHub