Skip to content

Toddle API

Client-facing REST API for discovering family-friendly activities in Singapore.

TypeScriptNode.jsPostgreSQL

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

bash
git clone --recurse-submodules https://github.com/tentou-tech/toddle-api.git
cd toddle-api
pnpm install

Configure

bash
# 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 minutes

Run

bash
pnpm dev

API 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, workshops
  • engine.toddle_activities — family-specific fields (age, price, hours, promotions)
  • engine.entity_embeddings — 768-dim vectors for RAG search
  • engine.entity_categories / engine.entity_tags — taxonomy junction tables

public schema (app-owned):

  • user_profiles, user_events — user identity and tracking
  • user_favorites, user_plans, user_plan_activities — user data
  • activity_feedback — likes, recommendations
  • chat_sessions, chat_messages — conversation history
  • categories, tags — taxonomy definitions

Entity graph example:

Singapore Zoo (venue)
  ├── Splash Safari (show)
  ├── Rainforest KidzWorld (exhibit)
  └── Keeper Talk (event)

API Endpoints

Public

EndpointMethodDescription
/api/activitiesGETBrowse with filters, venue grouping, parentVenue enrichment
/api/activities/searchGETFull-text search
/api/activities/rag-searchGETSemantic vector search
/api/activities/mapGETMap markers grouped by venue
/api/activities/statsGETCategory/source/location counts
/api/activities/:idGETSingle activity details
/api/chatPOSTChat assistant (rate-limited)
/api/chat/historyGETChat history (requires user ID)
/api/auth/meGETCurrent user info

User-Identified (X-Device-Id or Firebase Bearer)

EndpointMethodDescription
/api/recommendationsPOST/GETPersonalized home screen (rate-limited, cached)
/api/user-favoritesGET/POST/DELETEManage favorites
/api/user-plansGET/POSTManage activity plans
/api/user-activitiesGETActivity tracking
/api/activities/:id/feedbackPOSTSubmit feedback

Rate Limiting

Chat and recommendations are rate-limited per user:

TierChatRecommendations
free (default)30/hr10/hr
premium120/hr30/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 tools

Client 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.txt

LLM 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:

bash
cd packages/mcp
pnpm install && pnpm build

Claude Desktop config (claude_desktop_config.json):

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

bash
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 embeddings

Docker

bash
docker-compose up -d          # PostgreSQL + Redis + API
docker-compose logs -f api    # View logs

Includes PostgreSQL 15, Redis 7, API on port 3000, health checks, and persistent volumes.


Tech Stack

LayerTechnology
LanguageTypeScript 5.7
RuntimeNode.js 20+
FrameworkExpress 4.18
DatabasePostgreSQL 14+ with pgvector
ORMDrizzle ORM
LLMGoogle Gemini 2.5 Flash / Flash-Lite
EmbeddingsGemini Embedding (768-dim)
CachingRedis + in-memory fallback
AuthFirebase Admin SDK
DocsScalar (OpenAPI)
LLM IntegrationMCP Server, llms.txt

Documentation


License

MIT