Back

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.co

Deep Links Base URL

https://pointpoker.co/vote

All 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.

POST/api/rooms

Create 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

NameTypeRequiredDescription
facilitatorNamestringYesDisplay name of the facilitator (max 30 chars)
cardScale"fibonacci" | "tshirt" | "simple" | "custom"YesVoting scale to use
rolesstring[]YesParticipant roles (1-4 items, each 1-20 chars)
anonymousVotingbooleanYesWhether votes are anonymous until reveal
excludedFromEstimatestring[]NoRoles to exclude from estimate calculations (must be subset of roles)
autoRevealbooleanNoAuto-reveal votes when all participants have voted (default: false)
votingTimerSecondsnumberNoCountdown timer per story in seconds (15-300, omit for no timer)
facilitatorParticipatesbooleanNoWhether the facilitator can vote (default: false)
storyQueueQueuedStory[]NoPre-loaded stories to vote on (max 50 items). Each item: { title: string (1-200), description?: string (max 1000), url?: string (max 500) }
customCardValuesstring[]NoCustom card values when cardScale is "custom" (2-15 items, each 1-10 chars). Pro only.
theme"default" | "midnight" | "retro" | "neon"NoVisual theme for the room. Pro only.
passwordstringNoRoom password (max 50 chars). Pro only.
proTokenstringNoHMAC-SHA256 signed token for Pro feature verification

Response

NameTypeRequiredDescription
roomIdstringYesUnique room identifier (UUID)
roomCodestringYesShort shareable room code
joinUrlstringYesFull 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.

GET/api/rooms/code/:code

Resolve 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

NameTypeRequiredDescription
codestringYesThe room code to resolve

Response

NameTypeRequiredDescription
roomIdstringYesThe room ID for the given code

Example Response

{
  "roomId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

curl

curl https://api.pointpoker.co/api/rooms/code/ABC123

JavaScript

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.

GET/api/rooms/:id/info

Get 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

NameTypeRequiredDescription
idstringYesThe room ID

Response

NameTypeRequiredDescription
roomIdstringYesRoom identifier
roomCodestringYesRoom code for sharing
rolesstring[]YesAvailable roles in this room
cardScalestringYesVoting scale used in this room
participantCountnumberYesCurrent number of participants
maxParticipantsnumberYesMaximum allowed participants (25)
passwordRequiredbooleanNoWhether 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/info

JavaScript

const response = await fetch(
  "https://api.pointpoker.co/api/rooms/a1b2c3d4-e5f6-7890-abcd-ef1234567890/info"
);
const roomInfo = await response.json();
GET/health

Health check endpoint. Returns server status, uptime in seconds, and current timestamp.

Response

NameTypeRequiredDescription
status"ok"YesServer status
uptimenumberYesServer uptime in seconds
timestampstringYesISO 8601 timestamp

Example Response

{
  "status": "ok",
  "uptime": 86400,
  "timestamp": "2026-03-08T12:00:00.000Z"
}

curl

curl https://api.pointpoker.co/health

JavaScript

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/:roomId

Authentication Model

Platform endpoints use a three-tier token model. Each token type has different permissions and is scoped to a specific room.

TokenObtained FromPermissions
apiTokenPro subscription dashboardJoin rooms on behalf of platform users
facilitatorRestTokenRoom creation response (facilitator only)Reveal votes, end session, view status
participantTokenJoin endpoint responseSubmit votes, view status

Pass tokens via the Authorization header: Bearer <token>

POST/api/platform/:roomId/join

Join 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

NameTypeRequiredDescription
roomIdstringYesThe room ID to join

Request Body

NameTypeRequiredDescription
namestringYesDisplay name of the participant (max 30 chars)
rolestringYesParticipant role (must be in room's role list)
platformUserIdstringYesUnique user ID from the platform (e.g., Slack user ID)
platform"slack" | "teams" | "discord"YesPlatform identifier
asObserverbooleanNoJoin as non-voting observer (default: false)

Response

NameTypeRequiredDescription
participantIdstringYesUnique participant identifier
participantTokenstringYesToken for vote/status actions
roomCodestringYesRoom 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.

POST/api/platform/:roomId/vote

Submit 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

NameTypeRequiredDescription
roomIdstringYesThe room ID

Request Body

NameTypeRequiredDescription
valuestringYesCard value to vote (must be valid for room's scale)

Response

NameTypeRequiredDescription
successbooleanYesWhether the vote was accepted
voteCountnumberYesTotal votes cast so far
totalEligiblenumberYesTotal 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).

POST/api/platform/:roomId/reveal

Reveal all votes for the current story. Requires facilitator-level authorization.

Auth: Bearer facilitatorRestToken

Rate limit: 5 requests per minute per facilitator token

Path Parameters

NameTypeRequiredDescription
roomIdstringYesThe room ID

Response

NameTypeRequiredDescription
votesVoteResult[]YesArray of participant votes with names and values
overallOverallResultYesAggregate stats: average, median, consensus
byRoleRoleResult[]YesPer-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.

GET/api/platform/:roomId/status

Get 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

NameTypeRequiredDescription
roomIdstringYesThe room ID

Response

NameTypeRequiredDescription
currentStoryStory | nullYesCurrently active story or null
phase"waiting" | "voting" | "revealed"YesCurrent voting phase
voteCountnumberYesNumber of votes cast
totalEligiblenumberYesTotal eligible voters
participantCountnumberYesTotal participants in room
revealResultRevealResult | nullNoVote 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();
POST/api/platform/:roomId/end-session

End 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

NameTypeRequiredDescription
roomIdstringYesThe room ID

Response

NameTypeRequiredDescription
historyCompletedStory[]YesFull voting history for all stories in the session
totalStoriesnumberYesNumber of stories voted on
sessionDurationnumberYesSession 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.

Socket.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-room

Join 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-vote

Submit or change your vote for the current story.

{ value: string }  // Must be a valid card value for the room's scale

If autoReveal is enabled and all eligible voters have voted, votes are automatically revealed.

clear-vote

Clear your current vote.

set-storyFacilitator only

Set 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 only

Reveal all votes and compute statistics (average, median, consensus).

When the "open controls" feature is enabled, any non-observer participant can reveal.

next-storyFacilitator only

Move to the next story. Archives the current story to session history.

revoteFacilitator only

Clear all votes and restart voting on the current story.

add-to-queueFacilitator only

Add 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 only

Add multiple stories to the queue at once.

{ titles: string[] }  // Array of story titles (max 50 items)
start-next-from-queueFacilitator only

Start voting on the next story in the queue.

end-sessionFacilitator only

End the voting session. Broadcasts session history to all participants and closes the room.

Server → Client

Events you listen for from the server.

room-state

Full 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-joined

A new participant joined the room.

{
  id: string;
  name: string;
  role: string;
  isFacilitator: boolean;
  isObserver: boolean;
  connectionStatus: "online" | "offline";
  hasVoted: boolean;
}
vote-submitted

A participant submitted or changed their vote (value is hidden until reveal).

{ participantId: string }
votes-revealed

Votes 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-changed

The current story has changed.

{
  id: string;
  title: string;
  description: string | null;
  url: string | null;
  phase: "waiting" | "voting" | "revealed";
  timerEndsAt: string | null;
}
queue-updated

The story queue has been modified (added, removed, or reordered).

{
  queue: Array<{
    id: string;
    title: string;
    description: string | null;
    url: string | null;
  }>
}
session-ended

The 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;
  }>
}
error

An 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

ScopeLimitWindow
All /api/* endpoints100 requests1 minute per IP
POST /api/rooms10 requests15 minutes per IP
GET /api/rooms/code/:code30 requests1 minute per IP
GET /api/rooms/:id/info30 requests1 minute per IP

Socket.io Events

ScopeLimitWindow
Socket connections20 connections1 minute per IP
join-room3 events30 seconds per socket
submit-vote / clear-vote10 events10 seconds per socket
set-story5 events30 seconds per socket
reveal-votes / next-story / revote3 events30 seconds per socket
add-to-queue / remove-from-queue / update-queue-item10 events30 seconds per socket
reorder-queue5 events10 seconds per socket
end-session2 events60 seconds per socket
Password attempts5 attempts15 minutes per IP:room

Platform API

ScopeLimitWindow
POST .../join10 requests1 minute per API token
POST .../vote20 requests1 minute per participant token
POST .../reveal5 requests1 minute per facilitator token
GET .../status30 requests1 minute per token
POST .../end-session2 requests1 minute per facilitator token

Card Scales

Available card scales and their values. Use the scale ID when creating a room.

IDNameValues
fibonacciFibonacci0, 1, 2, 3, 5, 8, 13, 21, ☕, ❓
tshirtT-ShirtXS, S, M, L, XL, ☕, ❓
simpleSimple (1-10)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ☕, ❓
customCustom (Pro)2-15 user-defined values

Error Handling

All errors follow a consistent format.

HTTP Errors

{
  "error": "Human-readable error message"
}
StatusMeaning
400Invalid request body or parameters
403Pro feature used without valid Pro token
404Room or resource not found
429Rate 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).