FastAPI backend for Core — the all-in-one productivity platform.
cd core-api
make startThat's it! The server starts using uv.
API runs at http://localhost:8000
- Interactive docs:
http://localhost:8000/docs - Health check:
http://localhost:8000/api/health
# Install uv if you haven't already
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install runtime + dev dependencies using uv
uv pip install -r requirements-dev.txt
# Create .env file
cp .env.example .env
# Then edit .env with your Supabase credentialsCreate a .env file with these variables:
# Core Settings
API_ENV=development
DEBUG=False
# Supabase
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# Google OAuth (for Calendar and Gmail sync)
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
# Google Cloud Pub/Sub (for Gmail push notifications)
# Full topic path: projects/PROJECT_ID/topics/TOPIC_NAME
GOOGLE_PUBSUB_TOPIC=projects/YOUR_PROJECT_ID/topics/gmail-sync-topic
GOOGLE_CLOUD_PROJECT_ID=your_project_id # Optional fallback
# Webhooks
WEBHOOK_BASE_URL=https://your-api.vercel.app
# Cron Authentication
CRON_SECRET=your_random_secret
# AI Analysis
GROQ_API_KEY=your_groq_api_key
# Workspace invitations (Resend)
RESEND_API_KEY=your_resend_api_key
RESEND_FROM_DOMAIN=yourdomain.com
# Optional explicit sender (overrides RESEND_FROM_DOMAIN)
# RESEND_FROM_EMAIL=Core <invites@yourdomain.com>
# Local dev:
FRONTEND_URL=http://localhost:3000
# Production:
# FRONTEND_URL=https://core.so
# CORS (comma-separated for additional origins)
# ALLOWED_ORIGINS_ENV=https://yourapp.vercel.appcore-api/
├── api/
│ ├── config.py # Settings
│ ├── dependencies.py # Auth dependencies
│ ├── routers/ # HTTP endpoints (routing only)
│ │ ├── auth.py # Auth endpoints
│ │ ├── calendar.py # Calendar endpoints
│ │ ├── tasks.py # Tasks (Todoist-style, legacy)
│ │ ├── todos.py # Todos (Reminders-style, new)
│ │ ├── email.py # Email/Gmail endpoints
│ │ ├── chat.py # AI chat endpoints (streaming)
│ │ └── ...
│ └── services/ # Business logic (functional code)
│ ├── calendar/ # Calendar operations
│ ├── tasks/ # Task operations (legacy)
│ ├── todos/ # Todo operations (new)
│ │ ├── get_todos.py
│ │ ├── create_todo.py
│ │ ├── update_todo.py
│ │ ├── delete_todo.py
│ │ ├── complete_todo.py
│ │ ├── reorder_todos.py
│ │ ├── habit_helpers.py
│ │ └── calendar_sync.py
│ ├── chat/ # Chat/AI operations
│ │ ├── agent.py # LLM agent with tools
│ │ ├── content_builder.py # Content parts builder
│ │ ├── events.py # NDJSON event helpers
│ │ └── tools/ # Tool implementations
│ └── ...
├── lib/
│ └── supabase_client.py # Supabase client
├── supabase/
│ └── migrations/ # Database migrations
├── docs/
│ └── CONTENT_PARTS_SCHEMA.md # Content parts documentation
├── index.py # Main FastAPI app (Vercel entry)
├── requirements.txt # Runtime deps for Vercel
├── requirements-dev.txt # Local/CI dev deps layered on top
└── vercel.json
- routers/: HTTP layer - handles requests/responses, validation, HTTP status codes
- services/: Business logic layer - all functional operations, database interactions
- lib/: Shared utilities and clients
When adding new features:
- Create service class in
api/services/with business logic - Create router in
api/routers/that calls the service - Register router in
api/index.py
The chat system uses a Content Parts Schema for structured message content. This enables:
- Proper interleaving of text and tool outputs during streaming
- Identical rendering during streaming and after reload from database
- Easy extensibility for new tool types
Documentation: See docs/CONTENT_PARTS_SCHEMA.md for full details.
Messages contain a content_parts array with typed parts:
[
{"type": "text", "data": {"content": "Here are your emails:\n\n"}},
{"type": "display", "data": {"display_type": "emails", "items": [...], "total_count": 5}},
{"type": "text", "data": {"content": "\nAnd your todos:\n\n"}},
{"type": "display", "data": {"display_type": "todos", "items": [...], "total_count": 3}}
]-
Emit display event from your tool handler:
yield display_event(display_type="my_type", items=[...], total_count=10)
-
Add iOS renderer case in
ContentPartsRenderer.swift -
Add display content enum case in
ChatModels.swift
See the full guide in docs/CONTENT_PARTS_SCHEMA.md.
GET /- Health checkGET /api/health- Detailed health status
POST /auth/users- Create userPOST /auth/oauth-connections- Store OAuth tokensGET /auth/oauth-connections/{user_id}- Get user connections
GET /api/calendar/events- Get calendar eventsPOST /api/calendar/sync- Sync from Google Calendar
GET /api/tasks/- List tasksPOST /api/tasks/- Create taskPUT /api/tasks/{id}- Update taskDELETE /api/tasks/{id}- Delete task
GET /api/todos- List all todos (running list)GET /api/todos/today- Get todos due today + habits due todayGET /api/todos/habits- List habits onlyGET /api/todos/{id}- Get single todoPOST /api/todos- Create todo (201)POST /api/todos/habit- Create habit (201)PATCH /api/todos/{id}- Update todoDELETE /api/todos/{id}- Delete todoPATCH /api/todos/{id}/complete- Toggle completion (handles habit streaks)POST /api/todos/reorder- Reorder todos (atomic batch update)
GET /api/chat/conversations- List conversationsPOST /api/chat/conversations- Create conversationPATCH /api/chat/conversations/{id}- Update conversation titleDELETE /api/chat/conversations/{id}- Delete conversationGET /api/chat/conversations/{id}/messages- Get messagesPOST /api/chat/conversations/{id}/messages- Send message (streaming NDJSON response)
Migrations are in supabase/migrations/. Run them against your Supabase database:
# Via Supabase CLI
supabase db push
# Or directly via psql
psql $DATABASE_URL -f supabase/migrations/XXXXXX_migration_name.sqlKey tables:
todos- Reminders-style todos with habit supporthabit_completions- Tracks habit completion dates for streaksconversations- Chat conversationsmessages- Chat messages withcontent(text) andcontent_parts(structured) columns
RPC functions:
calculate_habit_streak(todo_id)- Efficient streak calculationreorder_todos(positions_json)- Atomic batch reorderbatch_get_habit_streaks(todo_ids[])- Batch streak lookup
The messages.content_parts column stores structured content:
-- JSONB column with GIN index
ALTER TABLE messages ADD COLUMN content_parts JSONB;
CREATE INDEX idx_messages_content_parts ON messages USING GIN (content_parts);See docs/CONTENT_PARTS_SCHEMA.md for the full schema.
# Run tests
uv run pytest
# Run with auto-reload
uv run python dev.py
# View logs
tail -f logs/api.logDeploy to Vercel:
# Install Vercel CLI
npm i -g vercel
# Deploy
vercelSet environment variables in Vercel dashboard:
SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYGOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETGOOGLE_PUBSUB_TOPIC(for Gmail push notifications)WEBHOOK_BASE_URL(set to https://your-api.vercel.app for production)CRON_SECRETGROQ_API_KEY(for AI email analysis)
- FastAPI - Modern Python web framework
- Supabase - PostgreSQL database + auth
- Pydantic - Data validation
- Mangum - ASGI adapter for serverless