choi.sh

Documentation

choi.sh exposes a REST API, an MCP server, and a CLI.

REST API

Base URL: https://choi.sh/api

EndpointDescription
GET /api/courtsFind courts (filters: surface, type, area, lat, lng, radius)
GET /api/courts/:slugCourt detail with schedules
GET /api/schedulesFind sessions (filters: day, skill, type)
GET /api/play-nowActive sessions right now
GET /api/recommendSmart recommendations (skill, day, time, location)
POST /api/submissionsSubmit a court or correction
POST /api/boardPost a player request
GET /api/boardBrowse open requests
POST /api/board/:id/respondRespond to a request (encrypted)
GET /api/board/:id/responsesGet responses (Ed25519 auth)
GET /api/board/:idGet a single board request
GET /api/board/archivedBrowse archived requests (admin)
GET /api/submissionsList submissions (admin)
PATCH /api/submissions/:idReview a submission (admin)
GET /api/telemetryGet 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.

ToolDescriptionParameters
find_courtsSearch courts with filterssurface, type, area, amenities, lat, lng, radius, limit, offset
get_courtCourt detail by slugslug
find_open_playFind sessions by day/skill/typeday, skill, type, limit, offset
play_nowActive sessions right nowlat, lng, radius
recommendSmart recommendationsskill, day, time, lat, lng, radius
submit_courtSubmit court or correctiontype, court_id, data
post_requestPost to the boardpublic_key, message, filters, ttl_hours
browse_requestsBrowse board requestsskill, area, q, limit, offset
respond_requestRespond to a requestrequest_id, encrypted_payload
get_responsesRetrieve responses (authed)request_id, timestamp, signature
review_submissionApprove or reject (admin)admin_key, submission_id, status, reviewer
list_submissionsList submissions by statusstatus, limit, offset
browse_archived_requestsBrowse archived (admin)admin_key, limit, offset
get_telemetryUsage 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

ScopeLimitStatus
Board posts3/day (new), 10/day (7+ days), 25/day (verified)enforced
Board responses50 per requestenforced
API global60 req/min per IPplanned
Submissions20/hourplanned