API Documentation
REST API for room management + WebSocket for real-time voting.
Base URL: https://poker.findutils.com
Quick Start
Create a room and get a shareable link in one API call:
curl -X POST https://poker.findutils.com/api/rooms \
-H "Content-Type: application/json" \
-d '{"sessionName":"Sprint 42","scale":"fibonacci","stories":[{"title":"Login page"}]}'
# Response (201)
{
"roomId": "abc12345",
"adminToken": "a31d6b88c640255218...",
"url": "/play#room=abc12345"
}Connect via WebSocket to vote in real-time:
const ws = new WebSocket('wss://poker.findutils.com/api/rooms/abc12345/ws?name=Alice');
ws.onmessage = (e) => console.log(JSON.parse(e.data));Base URL
https://poker.findutils.comAll endpoints are relative to this base URL. HTTPS required. The service runs across 300+ edge locations worldwide for sub-5ms latency.
Rate Limits
300 requests per minute per IP address. WebSocket connections are not rate limited after the initial handshake.
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed per minute |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
POST /api/rooms
Create a new planning poker room.
| Field | Type | Default | Description |
|---|---|---|---|
sessionName | string | "Planning Session" | Display name (max 100 chars) |
scale | string | "fibonacci" | fibonacci, modified_fibonacci, tshirt, powers_of_2, custom |
customScale | string[] | [] | Custom values (max 20, each max 10 chars) |
anonymousVoting | boolean | false | Hide voter identity until reveal |
autoRevealSeconds | number | 0 | -1 = auto when all voted, 0 = manual |
stories | array | [] | Stories with title and optional description |
premium | boolean | false | Enable premium features (50 voters, timer) |
Response (201):
{
"roomId": "abc12345",
"adminToken": "a31d6b88c640255218...",
"url": "/play#room=abc12345"
}The adminToken is required for admin actions (reveal, set estimate, remove story, etc.). Keep it secret. It is never returned by GET endpoints.
GET /api/rooms/:id
Get the current state of a room. The adminToken is never included in the response.
curl https://poker.findutils.com/api/rooms/abc12345Response includes: roomId, sessionName, scale, stories (with votes if revealed), voters (with online status), currentStoryIndex, phase, and tier.
POST /api/rooms/:id/stories
Add stories to an existing room. Max 200 stories per session.
curl -X POST https://poker.findutils.com/api/rooms/abc12345/stories \
-H "Content-Type: application/json" \
-d '{"stories":[{"title":"New feature"},{"title":"Bug fix","description":"Fix login timeout"}]}'POST /api/rooms/:id/export PRO
Export session results in CSV, Excel, or Markdown format. Requires API key or x402 payment.
curl -X POST https://poker.findutils.com/api/rooms/abc12345/export?format=csv \
-H "X-API-Key: sk_live_xxxx"WebSocket: Connect
Connect to a room for real-time voting.
wss://poker.findutils.com/api/rooms/:id/ws?name=YourNameOn connect, the server sends a state message with the full room state and your voterId. Other participants receive a voter_joined notification.
Optional token query parameter for authenticated sessions (JWT from the dashboard).
WebSocket: Client Messages
Send JSON messages to perform actions. Admin actions require the adminToken field.
| Type | Fields | Admin | Description |
|---|---|---|---|
vote | value | × | Cast a vote on the current story |
reveal | ✓ | Reveal all votes | |
set_estimate | value | ✓ | Set final estimate |
next_story | × | Move to next story | |
prev_story | × | Move to previous story | |
go_to_story | index | × | Jump to specific story |
reset_votes | ✓ | Clear votes on current story | |
add_story | title, description | × | Add a new story |
remove_story | storyId | ✓ | Remove a story |
update_settings | various | ✓ | Change room settings |
finish_session | ✓ | End session, save results | |
start_timer | seconds | ✓ | Start discussion timer |
pause_timer | ✓ | Pause timer | |
resume_timer | ✓ | Resume paused timer | |
reaction | emoji | × | Send emoji reaction |
ping | × | Keep-alive |
WebSocket: Server Messages
| Type | Description |
|---|---|
state | Full room state (on connect + after each change) |
voter_joined | Someone connected (voterId, name) |
voter_left | Someone disconnected |
reaction | Emoji from another voter |
timer_tick | Countdown update (remaining, running) |
timer_expired | Discussion time is up |
error | Error (permission denied, room full, etc.) |
pong | Response to ping |
Estimation Scales
| Scale | Values |
|---|---|
fibonacci | 1, 2, 3, 5, 8, 13, 21, ?, ☕ |
modified_fibonacci | 0, 0.5, 1, 2, 3, 5, 8, 13, 20, 40, 100, ?, ☕ |
tshirt | XS, S, M, L, XL, XXL, ?, ☕ |
powers_of_2 | 1, 2, 4, 8, 16, 32, 64, ?, ☕ |
custom | Defined by customScale array |
The ☕ card means "I need a break" and ? means "I don't know enough to estimate."
Voting Modes
Standard Voting
Votes are hidden until reveal. After reveal, you can see who voted what. Prevents anchoring bias while still allowing discussion about specific votes.
Anonymous Voting
Set anonymousVoting: true when creating the room. Even after reveal, votes are not attributed to specific voters. Useful when seniority dynamics might influence estimates.
Auto-Reveal
Set autoRevealSeconds: -1 to automatically reveal votes when all online voters have voted (minimum 2 votes). Set to a positive number for a countdown timer.
Discussion Timer PRO
Time-box discussions per story. Send start_timer with seconds (10-3600). The server broadcasts timer_tick every second and timer_expired when time is up.
// Start a 2-minute timer
ws.send(JSON.stringify({ type: 'start_timer', seconds: 120, adminToken: '...' }));
// Pause
ws.send(JSON.stringify({ type: 'pause_timer', adminToken: '...' }));
// Resume
ws.send(JSON.stringify({ type: 'resume_timer', adminToken: '...' }));Reactions
Send emoji reactions during the session. Allowed emojis:
👍 🤔 🔥 😂 ❓
ws.send(JSON.stringify({ type: 'reaction', emoji: '\u{1F525}' }));Free vs Pro
| Feature | Free | Pro |
|---|---|---|
| Rooms | Unlimited | Unlimited |
| Voters per room | 10 | 50 |
| Session history | 30 days | Unlimited |
| Scales | Standard (4) | All + custom |
| Anonymous voting | ✓ | ✓ |
| Discussion timer | × | ✓ |
| Velocity charts | × | ✓ |
| Export CSV/Excel | × | ✓ |
| Teams | 1 team, 5 members | Unlimited |
| Bulk import | × | ✓ |
x402 Micropayments
No subscription needed. Pay per use with USDC micropayments via the x402 protocol.
| Action | Price (USDC) |
|---|---|
| Create premium room | $0.005 |
| Export session | $0.002 |
| Velocity report | $0.003 |
| Bulk story import | $0.005 |
Include an X-PAYMENT header with a signed payment payload. The server responds with 402 and payment details if payment is required but missing.
API Keys
API key holders with a Pro plan get free access to all premium features without per-request payments.
curl -X POST https://poker.findutils.com/api/rooms \
-H "Content-Type: application/json" \
-H "X-API-Key: sk_live_xxxxxxxxxxxx" \
-d '{"sessionName":"Sprint 42","premium":true}'Get an API key from the dashboard.
JavaScript Example
// Create a room
const room = await fetch('https://poker.findutils.com/api/rooms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionName: 'Sprint 42',
scale: 'fibonacci',
stories: [{ title: 'Login page' }, { title: 'API auth' }],
}),
}).then(r => r.json());
// Connect via WebSocket
const ws = new WebSocket(
\`wss://poker.findutils.com/api/rooms/\${room.roomId}/ws?name=Alice\`
);
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.type) {
case 'state':
console.log('Room state:', msg.state);
break;
case 'voter_joined':
console.log(msg.name, 'joined');
break;
}
};
// Vote
ws.send(JSON.stringify({ type: 'vote', value: '5' }));
// Reveal (admin only)
ws.send(JSON.stringify({ type: 'reveal', adminToken: room.adminToken }));Python Example
import requests
import websocket
import json
# Create a room
room = requests.post('https://poker.findutils.com/api/rooms', json={
'sessionName': 'Sprint 42',
'scale': 'fibonacci',
'stories': [{'title': 'Login page'}],
}).json()
print(f"Room: {room['roomId']}")
# Connect via WebSocket
ws = websocket.create_connection(
f"wss://poker.findutils.com/api/rooms/{room['roomId']}/ws?name=Bot"
)
# Read initial state
state = json.loads(ws.recv())
print(f"Connected, {state['state']['onlineCount']} voters online")
# Vote
ws.send(json.dumps({'type': 'vote', 'value': '8'}))
ws.close()cURL Examples
# Create a room
curl -s -X POST https://poker.findutils.com/api/rooms \
-H "Content-Type: application/json" \
-d '{"sessionName":"Sprint 42","scale":"fibonacci"}' | jq .
# Get room state
curl -s https://poker.findutils.com/api/rooms/abc12345 | jq .
# Add stories
curl -s -X POST https://poker.findutils.com/api/rooms/abc12345/stories \
-H "Content-Type: application/json" \
-d '{"stories":[{"title":"Feature A"},{"title":"Bug B"}]}' | jq .
# Connect via WebSocket (using websocat)
websocat wss://poker.findutils.com/api/rooms/abc12345/ws?name=Alice