Extract clean, structured recipes from bloated recipe websites.
Paste a URL, get the recipe — no ads, no life stories, no popups. SousChat scrapes recipe pages, parses ingredients into structured data with scaling support, and uses vector embeddings for semantic search and similar recipe discovery.
Live at souschat.com
- Recipe extraction — paste any recipe URL and get a clean, structured recipe via JSON-LD parsing
- Ingredient parsing — quantities, units, and names broken out with support for fractions, ranges, and scaling
- Semantic search — find recipes by description using OpenAI embeddings and pgvector cosine similarity
- Similar recipes — discover related recipes based on title and ingredient embeddings
- Cook mode — distraction-free step-by-step view for following along while cooking
- Offline saves — save recipes to localStorage, no account required
| Backend | Frontend |
|---|---|
| FastAPI | React 19 |
| asyncpg | TypeScript |
| pgvector (PostgreSQL 16) | Tailwind CSS 4 |
OpenAI (text-embedding-3-large) |
Vite |
| httpx + cloudscraper | React Router |
| BeautifulSoup4 |
- Python 3.11+
- Node.js 18+
- Docker (for PostgreSQL + pgvector)
- An OpenAI API key
# Start PostgreSQL with pgvector
cd backend
docker compose up -d
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env # then edit with your valuesRequired environment variables (set in backend/.env):
DATABASE_URL=postgresql://dashcook:dashcook@localhost:5433/dashcook
OPENAI_API_KEY=your-key-here
CORS_ORIGINS=http://localhost:5173
RATE_LIMIT=30
# Run database migrations
# Apply SQL files in backend/db/migrations/ in order
# Start the server
uvicorn main:app --reloadThe API will be available at http://localhost:8000.
cd new-frontend
npm install
npm run devThe app will be available at http://localhost:5173.
Extract a recipe from a URL. Rate limited to 30 requests/hour per IP.
Request:
{
"url": "https://example.com/some-recipe"
}Response:
{
"title": "Spaghetti Carbonara",
"source_url": "https://example.com/some-recipe",
"image_url": "https://example.com/image.jpg",
"prep_time": "10 minutes",
"cook_time": "20 minutes",
"total_time": "30 minutes",
"servings": "4",
"ingredients": [
{
"raw": "2 cups all-purpose flour",
"name": "all-purpose flour",
"quantity": 2.0,
"quantity_max": null,
"unit": "cup",
"scalable": true
}
],
"instructions": ["Boil salted water...", "Cook the pasta..."]
}Find similar recipes by cosine distance on embeddings. Returns up to 20 results (default 5).
Response:
[
{
"title": "Pasta Primavera",
"source_url": "https://example.com/pasta-primavera",
"image_url": "https://example.com/img.jpg",
"distance": 0.123
}
]Semantic search across all stored recipes. Rate limited to 30 requests/hour per IP. Returns up to 50 results (default 10).
Response shape is the same as /similar.
├── backend/
│ ├── main.py # FastAPI app entry point
│ ├── config.py # Environment/settings (pydantic-settings)
│ ├── routes/
│ │ ├── health.py # GET /, GET /ping
│ │ └── recipes.py # POST /url, GET /similar, GET /search
│ ├── models/
│ │ ├── recipes.py # Recipe, Ingredient, SimilarRecipe
│ │ └── requests.py # ExtractRequest
│ ├── services/
│ │ ├── recipe_service.py # Orchestration: scrape → parse → embed → cache
│ │ ├── scraper.py # HTTP fetching with cloudscraper fallback
│ │ ├── parser.py # JSON-LD schema.org/Recipe extraction
│ │ ├── embedder.py # OpenAI embedding generation
│ │ └── ingredient_parser.py
│ ├── db/
│ │ ├── pool.py # asyncpg connection pool
│ │ ├── recipes.py # Query functions
│ │ └── migrations/ # SQL migration files
│ ├── middleware/
│ │ └── rate_limiter.py # IP-based sliding window rate limiter
│ └── tests/
├── new-frontend/
│ ├── src/
│ │ ├── App.tsx # Routes: /, /recipe/:id, /cook/:id
│ │ ├── api.ts # API client
│ │ ├── store.ts # localStorage persistence
│ │ ├── components/ # Topbar, Footer, RecipeCard, etc.
│ │ ├── pages/ # Home, RecipeDetail, CookNow
│ │ └── styles/ # CSS modules and tokens
│ └── vite.config.ts
├── .github/workflows/
│ └── deploy.yml # CI/CD: test → build → deploy
└── LICENSE # MIT
The backend deploys to Azure Container Apps via GitHub Actions CI/CD:
- Test — runs
pytestagainst the backend test suite - Build — builds a Docker image and pushes to Azure Container Registry
- Deploy — updates the Azure Container App with the new image
The frontend is deployed as a static site.
The pipeline triggers on pushes to main that modify files under backend/ or the workflow file itself.