Toddle API
Client-facing REST API for discovering family-friendly activities in Singapore.
Production: https://api.toddle.today/API Docs: https://api.toddle.today/api/docsLLM Docs: https://api.toddle.today/llms.txt
What It Does
Toddle serves enriched activity data for families with young children (ages 0-10) in Singapore. It reads from an entity-graph database managed by a separate data-engine service and exposes APIs for the mobile app and LLM clients.
Core features:
- Activity browsing — Filter by category, tags, age, price, location, entity type; venue grouping with parentVenue enrichment; map markers
- Semantic search — RAG-powered vector search using Gemini embeddings (768-dim, pgvector)
- Chat assistant — Context-aware conversational search with intent classification, conversation memory, and multi-turn support
- Recommendations — Personalized home screen with warm LLM greeting, context-aware activity suggestions, and caching
- User profiles — Child age, favorites, plans, feedback, activity tracking
- Rate limiting — Per-user configurable limits with subscription tier support
Quick Start
Prerequisites
- Node.js 20+
- PostgreSQL 14+ with pgvector extension
- pnpm
- Redis (optional — for caching and rate limiting; falls back to in-memory)
Install
git clone --recurse-submodules https://github.com/tentou-tech/toddle-api.git
cd toddle-api
pnpm installConfigure
# Database
DB_HOST=your-db-host
DB_PORT=5432
DB_NAME=wdwdtw
DB_USER=your-user
DB_PASSWORD=your-password
# API
PORT=3000
CORS_ORIGIN=*
API_KEY=your-secret-api-key # For protected endpoints
GEMINI_API_KEY=your-gemini-key # For RAG search, chat, and recommendations
REDIS_URL=redis://localhost:6379 # Optional
# Firebase (optional — for user auth)
FIREBASE_PROJECT_ID=your-project
FIREBASE_PRIVATE_KEY=your-key
FIREBASE_CLIENT_EMAIL=your-email
# Rate Limiting & Caching
RATE_LIMIT_CHAT_PER_HOUR=30
RATE_LIMIT_RECOMMEND_PER_HOUR=10
RATE_LIMIT_TIER_OVERRIDE= # Force tier for dev/testing
RECOMMEND_CACHE_TTL=900 # 15 minutesRun
pnpm devAPI docs at http://localhost:3000/api/docs
Architecture
Mobile App
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Toddle API (this repo) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ Activities │ │ Chat │ │ Recommendations │ │
│ │ Browse/Map │ │ Assistant │ │ Home Screen │ │
│ │ RAG Search │ │ Multi-turn │ │ Cached (15min) │ │
│ └──────┬──────┘ └──────┬───────┘ └────────┬──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Shared Infrastructure │ │
│ │ • Enriched Context (profile + spatial + temporal │ │
│ │ + behavioral + conversation) │ │
│ │ • Gemini LLM (classification + generation) │ │
│ │ • Vector Search (pgvector embeddings) │ │
│ │ • Rate Limiting + Caching (Redis) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
└─────────┼───────────────────────────────────────────────────┘
▼
┌─────────────────────────────────┐
│ PostgreSQL │
│ • engine.entities (read-only) │
│ • engine.entity_embeddings │
│ • user_profiles, favorites, │
│ plans, chat_sessions, etc. │
└─────────────────────────────────┘Data Model
The API reads from two database schemas:
engine schema (read-only, owned by data-engine):
engine.entities— graph nodes: venues, shows, events, exhibits, workshopsengine.toddle_activities— family-specific fields (age, price, hours, promotions)engine.entity_embeddings— 768-dim vectors for RAG searchengine.entity_categories/engine.entity_tags— taxonomy junction tables
public schema (app-owned):
user_profiles,user_events— user identity and trackinguser_favorites,user_plans,user_plan_activities— user dataactivity_feedback— likes, recommendationschat_sessions,chat_messages— conversation historycategories,tags— taxonomy definitions
Entity graph example:
Singapore Zoo (venue)
├── Splash Safari (show)
├── Rainforest KidzWorld (exhibit)
└── Keeper Talk (event)API Endpoints
Public
| Endpoint | Method | Description |
|---|---|---|
/api/activities | GET | Browse with filters, venue grouping, parentVenue enrichment |
/api/activities/search | GET | Full-text search |
/api/activities/rag-search | GET | Semantic vector search |
/api/activities/map | GET | Map markers grouped by venue |
/api/activities/stats | GET | Category/source/location counts |
/api/activities/:id | GET | Single activity details |
/api/chat | POST | Chat assistant (rate-limited) |
/api/chat/history | GET | Chat history (requires user ID) |
/api/auth/me | GET | Current user info |
User-Identified (X-Device-Id or Firebase Bearer)
| Endpoint | Method | Description |
|---|---|---|
/api/recommendations | POST/GET | Personalized home screen (rate-limited, cached) |
/api/user-favorites | GET/POST/DELETE | Manage favorites |
/api/user-plans | GET/POST | Manage activity plans |
/api/user-activities | GET | Activity tracking |
/api/activities/:id/feedback | POST | Submit feedback |
Rate Limiting
Chat and recommendations are rate-limited per user:
| Tier | Chat | Recommendations |
|---|---|---|
free (default) | 30/hr | 10/hr |
premium | 120/hr | 30/hr |
Response headers: X-RateLimit-Limit, X-RateLimit-Remaining. Returns 429 with Retry-After when exceeded.
Chat Pipeline
The chat endpoint uses a 6-step context-aware pipeline:
[1] Load Conversation History (sessionId, 2-hour freshness window)
[2] Build Enriched Context (profile + spatial + temporal + behavioral)
[3] Classify Intent (heuristic first, then Gemini flash-lite)
[4] Route to Handler (search / filter / follow_up / clarification / action / general)
[5] Generate Response (Gemini flash)
[6] Enrich & Return (parentVenue, follow-up suggestions, save history)Intents: search, filter, follow_up, clarification, action, general
See docs/CHAT_PIPELINE.md for full details.
Project Structure
src/
├── api/routes/
│ ├── processed-activities.routes.ts # Activity browsing, search, map, stats
│ ├── chat.routes.ts # Chat assistant
│ ├── recommendations.routes.ts # Home screen recommendations
│ ├── activity-feedback.routes.ts # User feedback
│ ├── auth.routes.ts # Firebase auth
│ ├── user-activities.routes.ts # Activity tracking
│ ├── user-favorites.routes.ts # Favorites
│ ├── user-plans.routes.ts # Plans
│ └── users.routes.ts # User profiles (protected)
├── services/
│ ├── activities/ # Retriever, embeddings, entity CRUD
│ ├── chat/ # Chat pipeline, context, intent, history
│ ├── infra/ # Gemini, embedding, cache, Redis, Firebase
│ └── user/ # Profiles, context, events, favorites, plans
├── middleware/
│ ├── firebase-auth.middleware.ts # Optional Firebase auth
│ ├── rate-limit.middleware.ts # Per-user rate limiting
│ ├── user-profile.middleware.ts # Auto-create user profile
│ └── activity-tracking.middleware.ts # Track user API calls
├── config/
│ ├── environment.ts # Env var config
│ ├── rate-limits.ts # Rate limit tiers
│ └── swagger.ts # OpenAPI spec
├── database/
│ ├── engine-schema.ts # Read-only engine schema
│ ├── schema.drizzle.ts # App-owned tables
│ └── db.drizzle.ts # Drizzle connection
├── utils/
└── server.ts
packages/
├── rag/ # Shared RAG types, embedding provider, helpers
├── api-spec/ # OpenAPI base definition (git submodule)
└── mcp/ # MCP server — exposes API as LLM-callable toolsClient Integration
API Documentation (Scalar)
Interactive API docs with a "Try It" playground are served at /api/docs. The OpenAPI JSON spec is at /api/docs.json.
llms.txt
A concise, machine-readable API description following the llms.txt convention:
GET /llms.txtLLM clients can fetch this to understand the API without parsing OpenAPI.
MCP Server
The packages/mcp package exposes the API as callable tools for LLM clients (Claude Desktop, etc.) via the Model Context Protocol.
Setup:
cd packages/mcp
pnpm install && pnpm buildClaude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"toddle": {
"command": "node",
"args": ["/path/to/toddle/packages/mcp/dist/index.js"],
"env": {
"TODDLE_API_URL": "https://api.toddle.today",
"TODDLE_DEVICE_ID": "my-device-id"
}
}
}
}Tools: toddle_chat, toddle_search_activities, toddle_browse_activities, toddle_get_recommendations, toddle_get_activity
Commands
pnpm dev # Development server
pnpm build # TypeScript compilation
pnpm start # Production server
pnpm test # Run tests
pnpm run db:push # Push schema to database
pnpm run db:studio # Drizzle Studio GUI
pnpm run test:chat # Chat API tests
pnpm run test:chat:scenarios # Chat scenario runner
pnpm run embed:activities # Generate embeddingsDocker
docker-compose up -d # PostgreSQL + Redis + API
docker-compose logs -f api # View logsIncludes PostgreSQL 15, Redis 7, API on port 3000, health checks, and persistent volumes.
Tech Stack
| Layer | Technology |
|---|---|
| Language | TypeScript 5.7 |
| Runtime | Node.js 20+ |
| Framework | Express 4.18 |
| Database | PostgreSQL 14+ with pgvector |
| ORM | Drizzle ORM |
| LLM | Google Gemini 2.5 Flash / Flash-Lite |
| Embeddings | Gemini Embedding (768-dim) |
| Caching | Redis + in-memory fallback |
| Auth | Firebase Admin SDK |
| Docs | Scalar (OpenAPI) |
| LLM Integration | MCP Server, llms.txt |
Documentation
- API_DOCUMENTATION.md — Full API reference
- CHAT_PIPELINE.md — Chat architecture, intents, context, rate limiting
- RAG_FLOW.md — Embeddings, vector search, recommendations
- DATA_FLOW.md — Architecture and data flow
License
MIT