This is the service that powers Caliper behind the scenes. The app that users
see is in Caliper-Frontend; this backend stores and protects nutrition data,
looks up foods, calculates totals, and talks to AI services.
In plain terms: the frontend is the interface, and this backend is the engine.
Caliper depends on the backend for the core nutrition experience:
- Keeps each user's meal logs private and separate.
- Calculates daily calories and macros from logged foods.
- Groups logs by local date and timezone.
- Searches foods through USDA FoodData Central.
- Looks up packaged foods by barcode through Open Food Facts.
- Saves custom foods.
- Analyzes meal photos with AI.
- Runs the AI nutrition advisor and saves conversation history.
- Tracks weight entries over time.
- Calculates calorie and macro targets with a TDEE calculator.
- Returns helpful error messages when an external service is unavailable.
Most users never interact with this project directly, but they rely on it every time they log food, scan a barcode, open the dashboard, or ask the advisor a question.
Users sign in through Supabase. The frontend sends the user's Supabase token to this backend, and the backend checks that token before allowing access to any private data.
Database policies also protect user data so one user cannot read another user's logs, profile, advisor messages, custom foods, or weight entries.
The backend aggregates the foods a user logged for a day and returns:
- calories eaten
- calories remaining
- protein, carbs, and fat eaten
- macro targets
- editable meal-log rows
Dates are calculated using the user's timezone, so "today" matches the user's actual day.
The backend can:
- Search USDA foods by text.
- Find packaged foods by barcode.
- Normalize nutrition values so the app can calculate nutrients for any amount in grams.
When a user uploads or captures a meal photo, the backend sends the image to OpenRouter and asks for structured food estimates. The response is checked before it is sent back to the app.
The advisor uses context from the user's own data:
- today's logged foods
- today's remaining macros
- recent calorie and macro history
- previous messages in the current conversation
Conversation history is saved so the user can come back to older chats.
The backend stores dated weight entries and calculates TDEE-based calorie and macro targets from profile details such as age, height, weight, activity level, and goal.
You need accounts/API keys for:
- Supabase, for accounts and the database
- USDA FoodData Central, for food search
- OpenRouter, for AI meal analysis and advisor chat
- Python 3.13
- A Supabase project
- PostgreSQL connection credentials from Supabase
- USDA FoodData Central API key
- OpenRouter API key
Create a virtual environment:
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1Install dependencies:
python -m pip install -r requirements.txtCreate a local environment file:
Copy-Item .env.example .env.localFill in .env.local:
DATABASE_URL=postgresql://postgres:password@db.example.supabase.co:5432/postgres
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_JWT_SECRET=
USDA_API_KEY=your-usda-key
OPENROUTER_API_KEY=your-openrouter-key
OPENROUTER_MODEL=
OPENROUTER_APP_URL=
OPENROUTER_APP_NAME=
CORS_ORIGINS=http://localhost:8081,http://localhost:19006Start the API:
uvicorn main:app --reload --host 0.0.0.0 --port 8000Useful local URLs:
- API docs:
http://localhost:8000/docs - Machine-readable API schema:
http://localhost:8000/openapi.json - Health check:
http://localhost:8000/health
The backend loads .env.local and .env automatically during local
development. Environment variables already set by the operating system or Vercel
take priority.
The database migrations live in:
supabase/migrations/
They create the tables, indexes, helper functions, triggers, and Row Level Security rules for:
- profiles
- custom foods
- meal logs
- advisor conversations
- advisor messages
- weight logs
Apply them with the Supabase CLI:
supabase link --project-ref your-project-ref
supabase db pushThe migrations assume a Caliper database schema. Review them carefully before applying them to an existing production database.
DATABASE_URL: PostgreSQL connection string used by the backend.SUPABASE_URL: Supabase project URL, also used for modern JWT verification.SUPABASE_JWT_SECRET: JWT secret for older HS256 Supabase projects. Leave blank if your project uses JWKS/asymmetric signing.USDA_API_KEY: enables food search.OPENROUTER_API_KEY: enables meal photo analysis and advisor chat.OPENROUTER_APP_URL: optional OpenRouter attribution URL.OPENROUTER_APP_NAME: optional OpenRouter attribution name.CORS_ORIGINS: web origins allowed to call the backend from a browser.
Native iOS and Android requests are not limited by browser CORS rules, but Expo Web is.
Never expose database passwords, OpenRouter keys, USDA keys, or Supabase service-role keys in the frontend.
All /api/v1 routes require a valid Supabase bearer token unless noted
otherwise.
GET /api/v1/dashboard?timezone=Europe/BucharestReturns today's nutrition totals and editable meal logs for the signed-in user.
POST /api/v1/meal-logs
PATCH /api/v1/meal-logs/{log_id}
DELETE /api/v1/meal-logs/{log_id}Meal logs store the original food nutrition values and the amount eaten. If a user changes the amount later, nutrients are recalculated from the original per-100 g values.
GET /api/v1/food/search?query=chicken
GET /api/v1/food/barcode/3017620422003Search uses USDA FoodData Central. Barcode lookup uses Open Food Facts.
POST /api/v1/ai/analyze-plateAccepts image data and returns estimated foods, calories, protein, carbs, fats, and a confidence explanation.
GET /api/v1/ai/conversations
GET /api/v1/ai/chat
POST /api/v1/ai/chatThe backend owns advisor history and sends relevant nutrition context to the AI model. User and assistant messages are saved together after a successful response.
GET /api/v1/weight-logs
POST /api/v1/weight-logs
DELETE /api/v1/weight-logs/{log_id}Saving a new entry for a date that already has one updates that date's weight.
POST /api/v1/profile/tdeeUses the Mifflin-St Jeor equation, an activity multiplier, and the selected goal to estimate calorie and macro targets.
Errors use a consistent shape:
{
"error": {
"code": "external_service_unavailable",
"message": "USDA FoodData Central: Food search is temporarily unavailable."
}
}The backend handles common cases such as invalid input, missing resources, authentication problems, invalid timezones, external API failures, malformed external API responses, and unexpected internal failures.
Unexpected technical details are logged on the server but are not shown to the user.
app/
core/
config.py Reads settings from environment variables
database.py Database connection management
errors.py Shared error types
security.py Supabase token verification
routers/ API route definitions
schemas/ Request and response shapes
services/ Food, meal, AI, profile, and weight logic
supabase/
migrations/ Database schema and security rules
main.py FastAPI application entry point
Routers focus on HTTP requests and responses. Services contain most of the business logic, database work, external API calls, and AI payload handling.
- Python 3.13
- FastAPI
- Uvicorn
- Pydantic 2
- asyncpg
- httpx
- PyJWT
- Supabase PostgreSQL and Auth
- Open Food Facts
- USDA FoodData Central
- OpenRouter
The backend and frontend should be deployed as separate Vercel projects. Deploy the backend first, then point the frontend to this backend's production URL.
Apply the database migrations and collect:
- Supabase project URL
- PostgreSQL connection string
- Supabase JWT secret, if your project uses HS256 signing
For serverless deployments, use the Supabase transaction pooler connection
string. If needed, add ?sslmode=require.
Import this repository into Vercel and configure:
- Root Directory: repository root
- Framework Preset: FastAPI if detected, otherwise Other
- Python version: 3.13 where available
vercel.json routes requests to main.py.
DATABASE_URL=postgresql://...
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_JWT_SECRET=
USDA_API_KEY=your-usda-key
OPENROUTER_API_KEY=your-openrouter-key
OPENROUTER_APP_URL=https://your-frontend.vercel.app
OPENROUTER_APP_NAME=Caliper
CORS_ORIGINS=https://your-frontend.vercel.appDo not give untrusted preview deployments production database credentials.
Push to the production branch or run:
npx vercel --prodVerify:
https://your-backend.vercel.app/health
https://your-backend.vercel.app/docs
Set the frontend environment variable:
EXPO_PUBLIC_API_URL=https://your-backend.vercel.app/api/v1Also set backend CORS_ORIGINS to the exact frontend origin.
- External provider failures return typed errors instead of raw provider exceptions.
- Daily aggregation uses the timezone supplied by the app.
- The database pool is created lazily, so
/healthcan start even before the first database-backed request. - CORS should be restricted to known production frontend origins.
OPENROUTER_MODELis optional and defaults toopenrouter/free; set it to a specific model ID when you want to pin provider behavior.