Documentation
choi.sh exposes a REST API, an MCP server, and a CLI.
REST API
Base URL: https://choi.sh/api
| Endpoint | Description |
|---|---|
GET /api/courts | Find courts (filters: surface, type, area, lat, lng, radius) |
GET /api/courts/:slug | Court detail with schedules |
GET /api/schedules | Find sessions (filters: day, skill, type) |
GET /api/play-now | Active sessions right now |
GET /api/recommend | Smart recommendations (skill, day, time, location) |
POST /api/submissions | Submit a court or correction |
POST /api/board | Post a player request |
GET /api/board | Browse open requests |
POST /api/board/:id/respond | Respond to a request (encrypted) |
GET /api/board/:id/responses | Get responses (Ed25519 auth) |
GET /api/board/:id | Get a single board request |
GET /api/board/archived | Browse archived requests (admin) |
GET /api/submissions | List submissions (admin) |
PATCH /api/submissions/:id | Review a submission (admin) |
GET /api/telemetry | Get usage telemetry (admin) |
Endpoint Reference
Copy-paste examples for each endpoint.
GET /api/courts
/api/courts?surface=indoor&type=public&area=memorial&limit=5
Params: surface (concrete|asphalt|sport-court|indoor), type (public|private|club), area, amenities (comma-separated), lat, lng, radius, limit, offset
GET /api/courts/:slug
/api/courts/memorial-park-pickleball
Returns court detail with schedules.
GET /api/schedules
/api/schedules?day=tuesday&skill=3.5&type=open_play
Params: day (monday-sunday), skill (1.0-5.0), type (open_play|league|lessons|reserved), limit, offset
GET /api/play-now
/api/play-now
Returns sessions happening right now. Optional: lat, lng, radius for proximity sorting.
GET /api/recommend
/api/recommend?skill=3.5&day=tuesday&time=evening
Params: skill (1.0-5.0), day, time (morning|afternoon|evening), lat, lng, radius, limit. At least one of skill, day, or lat/lng required.
GET /api/board
/api/board?skill=3.5&area=memorial&q=doubles
Params: skill, area, q (text search), limit, offset
GET /api/board/:id
/api/board/550e8400-e29b-41d4-a716-446655440000
Returns a single board request with response count.
Response Format
{
"ok": true,
"data": { ... },
"meta": {
"count": 15,
"timestamp": "2026-02-04T..."
}
}
Error Response
{
"ok": false,
"data": null,
"error": {
"message": "lat and lng must be provided together",
"code": "VALIDATION_ERROR"
}
}
Example Response
GET /api/courts?surface=indoor&limit=2
{
"ok": true,
"data": [
{
"id": "abc123",
"name": "Memorial Park Pickleball",
"slug": "memorial-park-pickleball",
"address": "7400 Memorial Dr, Houston, TX",
"court_count": 8,
"surface": "indoor",
"type": "public",
"cost": "Free",
"amenities": ["lights", "nets", "restrooms"]
}
],
"meta": {
"count": 1,
"offset": 0,
"limit": 2,
"timestamp": "2026-02-18T12:00:00Z"
}
}
POST Body Examples
POST /api/board
{
"public_key": "base64-encoded-ed25519-public-key",
"message": "Looking for 3.5 doubles Tuesday evening near Memorial",
"filters": { "skill": 3.5, "area": "memorial", "day": "tuesday" },
"ttl_hours": 48
}
public_key (required) Ed25519 public key, base64 | message (required) max 2000 chars | filters (optional) | ttl_hours (optional) 1-720, default 24
POST /api/submissions
{
"type": "new_court",
"data": {
"name": "Westchase Pickleball",
"address": "1000 Westchase Blvd, Houston, TX",
"court_count": 4,
"surface": "concrete",
"type": "public"
}
}
type (required) new_court | schedule_update | correction | court_id (required for schedule_update, correction) | data (required) court/schedule data object
MCP Server
Endpoint: POST https://choi.sh/mcp
Streamable HTTP transport, stateless per-request.
| Tool | Description | Parameters |
|---|---|---|
find_courts | Search courts with filters | surface, type, area, amenities, lat, lng, radius, limit, offset |
get_court | Court detail by slug | slug |
find_open_play | Find sessions by day/skill/type | day, skill, type, limit, offset |
play_now | Active sessions right now | lat, lng, radius |
recommend | Smart recommendations | skill, day, time, lat, lng, radius |
submit_court | Submit court or correction | type, court_id, data |
post_request | Post to the board | public_key, message, filters, ttl_hours |
browse_requests | Browse board requests | skill, area, q, limit, offset |
respond_request | Respond to a request | request_id, encrypted_payload |
get_responses | Retrieve responses (authed) | request_id, timestamp, signature |
review_submission | Approve or reject (admin) | admin_key, submission_id, status, reviewer |
list_submissions | List submissions by status | status, limit, offset |
browse_archived_requests | Browse archived (admin) | admin_key, limit, offset |
get_telemetry | Usage telemetry (admin) | admin_key, days, interface, extended |
Authentication
Most endpoints are public. Board responses require Ed25519 signature proof:
- Header
Authorization: Signed {request_id}:{timestamp}:{signature}- Signature payload
{request_id}:{timestamp}- Algorithm
- Ed25519 (Web Crypto)
- Window
- 5 minutes
Board Crypto Flow
| 1. | Poster generates Ed25519 keypair, publishes public key with request |
| 2. | Responder encrypts message with poster's public key (X25519) |
| 3. | Poster retrieves responses by signing challenge with private key |
| 4. | Poster decrypts responses with private key |
Rate Limits
| Scope | Limit | Status |
|---|---|---|
| Board posts | 3/day (new), 10/day (7+ days), 25/day (verified) | enforced |
| Board responses | 50 per request | enforced |
| API global | 60 req/min per IP | planned |
| Submissions | 20/hour | planned |