Telegram-first nutrition tracking with photo-based food detection and P/F/C macros.
Photos are processed only during active sessions and are never stored.
- A user sends a food photo to the Telegram bot.
- The bot proposes detected items and guesses based on the user’s library.
- The user confirms items, chooses products (library → USDA FDC), or enters manually.
- The user confirms or enters grams.
- The bot shows a macro summary and saves the meal.
- The food is added to the user’s library for faster future matches.
- Vision: The image is sent once to an OpenAI model with Structured Outputs to extract item candidates.
- Selection: For each item, the bot suggests:
- the best match from the user’s library
- fallback FDC results
- manual entry (name + macros)
- Portions: Accept estimated grams or enter grams.
- Summary: Per-item macros and totals are shown before saving.
- Storage: Only meal logs and library entries are stored in Supabase Postgres. No images.
- Backend: FastAPI
- AI: OpenAI Responses API (vision + Structured Outputs)
- Nutrition DB: USDA FoodData Central (FDC)
- Storage: Supabase Postgres
- Frontend: Telegram bot with inline keyboards
/start— create user and set timezone/today— totals + meal list/week— daily totals + averages/month— daily totals + averages/history— last 10 meals (tap to view + edit grams)/library— top foods + add manual entry/cancel— cancel active session
- Talk to
@BotFather→ create a bot → copy the token.
- Create a new project in Supabase.
- Copy:
- Project URL →
SUPABASE_URL - Service Role Key →
SUPABASE_SERVICE_KEY
- Project URL →
Apply the migration:
psql "<YOUR_SUPABASE_CONNECTION_STRING>" -f supabase/migrations/0001_mvp.sql
- OpenAI API key →
OPENAI_API_KEY - USDA FoodData Central key (data.gov) →
FDC_API_KEY
The app automatically loads .env.<ENVIRONMENT> first, then .env.
Create two files:
.env.local
.env.production
Example .env.local:
ENVIRONMENT=local
TELEGRAM_BOT_TOKEN=...
SUPABASE_URL=http://127.0.0.1:54321
SUPABASE_SERVICE_KEY=<local_service_role_key>
ADMIN_TOKEN=...
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-5.2
OPENAI_REASONING_EFFORT=high
OPENAI_STORE=false
FDC_API_KEY=...
FDC_BASE_URL=https://api.nal.usda.gov/fdc/v1
Example .env.production:
ENVIRONMENT=production
TELEGRAM_BOT_TOKEN=...
SUPABASE_URL=https://<your-prod-project>.supabase.co
SUPABASE_SERVICE_KEY=<prod_service_role_key>
ADMIN_TOKEN=...
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-5.2
OPENAI_REASONING_EFFORT=high
OPENAI_STORE=false
FDC_API_KEY=...
FDC_BASE_URL=https://api.nal.usda.gov/fdc/v1
To run in a specific environment:
ENVIRONMENT=local uvicorn nutrition_tracker.api.asgi:app --reload
or
ENVIRONMENT=production uvicorn nutrition_tracker.api.asgi:app
Notes:
OPENAI_STORE=falseensures model responses are not stored by OpenAI.- Photos are never stored in Supabase or on disk.
TELEGRAM_ALLOWED_USER_IDScontrols who can use the bot (*or empty = everyone, comma-separated IDs = allowlist).
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
uvicorn nutrition_tracker.api.asgi:app --reload
Or use helper scripts:
bin/run_dev.sh
bin/run_prod.sh
bin/test.sh
bin/lint.sh
bin/format_check.sh
bin/supabase_start.sh
Telegram requires a public HTTPS URL (no localhost).
- Run the app:
bin/run_dev.sh
- Start ngrok:
ngrok http 8000
- Set the webhook using the HTTPS URL ngrok prints:
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=https://<ngrok-id>.ngrok-free.app/telegram/webhook
- Verify:
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getWebhookInfo
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=https://<your-domain>/telegram/webhook
This repo is Vercel-ready with a serverless Python entrypoint.
- Create a new Vercel project and import this repository.
- Vercel will detect
api/index.pyand use the Python runtime automatically.
In Vercel → Project Settings → Environment Variables, set:
TELEGRAM_BOT_TOKENSUPABASE_URLSUPABASE_SERVICE_KEYADMIN_TOKENOPENAI_API_KEYOPENAI_MODELOPENAI_REASONING_EFFORTOPENAI_STOREFDC_API_KEYFDC_BASE_URL
Just push to main. Vercel builds and deploys automatically.
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/setWebhook?url=https://<your-vercel-domain>/telegram/webhook
- Serverless entrypoint:
api/index.py - Vercel uses
requirements.txtfor dependencies. - Vercel’s Python runtime is 3.12, so the project targets
>=3.12.
Open:
GET /admin/ui
You’ll be prompted for your admin token. The UI calls:
GET /admin/usersGET /admin/sessionsGET /admin/costs
- User sends photo.
- Bot: “I think I see: rice, chicken… Does this look right?”
- User can “Looks right” or “Fix items”.
- For each item, user picks:
- library match
- FDC option
- manual entry
- Bot asks for grams per item.
- Bot shows macro summary and asks to save.
pytest
pytest --cov --cov-report=term-missing
ruff check src tests
ruff format --check src tests
- Images are never stored.
- Telegram file IDs are removed after session completion.
- OpenAI responses use
store=false.
- 401 on /admin/: check
X-Admin-TokenmatchesADMIN_TOKEN. - Bot is silent: webhook isn’t set or isn’t reachable.
- Vision fails: verify
OPENAI_API_KEYand model name.