# API Reference Complete reference for the WineBox REST API. ## Base URL ``` http://localhost:8000/api ``` ## Authentication All API endpoints (except `/health`) require JWT authentication. ### Getting a Token ```bash # Note: OAuth2 spec uses 'username' field, but WineBox expects email curl -X POST http://localhost:8000/api/auth/token \ -d "username=myemail@example.com&password=mypass" ``` **Response**: ```json { "access_token": "eyJ...", "token_type": "bearer" } ``` ### Using the Token Include the token in the `Authorization` header for all requests: ```bash curl -H "Authorization: Bearer " \ http://localhost:8000/api/wines ``` ### Token Expiration Tokens expire after 30 minutes. Request a new token when needed. ## Endpoints ### Health Check #### GET /health Check if the server is running. **Response**: ```json { "status": "healthy", "version": "0.5.12", "app_name": "WineBox" } ``` --- ## Wine Endpoints ### POST /api/wines/record Check in wine bottles to the cellar. **Content-Type**: `multipart/form-data` **Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | front_label | file | Yes | Front label image | | back_label | file | No | Back label image | | name | string | No | Wine name (auto-detected if not provided) | | winery | string | No | Winery name | | vintage | integer | No | Vintage year (1900-2100) | | grape_variety | string | No | Grape variety | | region | string | No | Wine region (e.g., Burgundy, Napa Valley) | | sub_region | string | No | Sub-region (e.g., Côte de Nuits, Médoc) | | appellation | string | No | Appellation (e.g., Nuits-St-Georges, Pomerol) | | classification | string | No | Classification (e.g., Grand Cru, DOCG, Reserve) | | country | string | No | Country of origin | | alcohol_percentage | float | No | Alcohol percentage (0-100) | | quantity | integer | Yes | Number of bottles (min: 1) | | notes | string | No | Check-in notes | **Response**: `201 Created` ```json { "id": "uuid", "name": "Wine Name", "winery": "Winery Name", "vintage": 2019, "grape_variety": "Cabernet Sauvignon", "region": "Burgundy", "sub_region": "Côte de Nuits", "appellation": "Nuits-St-Georges", "country": "France", "classification": "Premier Cru", "alcohol_percentage": 14.5, "front_label_text": "OCR extracted text...", "back_label_text": null, "front_label_image_path": "uuid.jpg", "back_label_image_path": null, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z", "inventory": { "quantity": 6, "updated_at": "2024-01-15T10:30:00Z" } } ``` --- ### POST /api/wines/{wine_id}/checkout Check out wine bottles from the cellar. **Content-Type**: `multipart/form-data` **Path Parameters**: - `wine_id`: UUID of the wine **Form Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | quantity | integer | Yes | Number of bottles to remove (min: 1) | | notes | string | No | Check-out notes | **Response**: `200 OK` Returns the updated wine object. **Errors**: - `404 Not Found`: Wine not found - `400 Bad Request`: Not enough bottles in stock --- ### GET /api/wines List all wines. **Query Parameters**: | Name | Type | Description | |------|------|-------------| | skip | integer | Number of records to skip (default: 0) | | limit | integer | Maximum records to return (default: 100) | | in_stock | boolean | Filter by stock status | **Response**: `200 OK` ```json [ { "id": "uuid", "name": "Wine Name", ... "inventory": { "quantity": 3, "updated_at": "..." } } ] ``` --- ### GET /api/wines/{wine_id} Get wine details with full transaction history. **Response**: `200 OK` ```json { "id": "uuid", "name": "Wine Name", ... "inventory": {...}, "transactions": [ { "id": "uuid", "transaction_type": "CHECK_IN", "quantity": 6, "notes": "Purchased at auction", "transaction_date": "2024-01-15T10:30:00Z" } ] } ``` --- ### PUT /api/wines/{wine_id} Update wine metadata. **Content-Type**: `application/json` **Request Body**: ```json { "name": "Updated Name", "vintage": 2020, "grape_variety": "Merlot", "sub_region": "Côte de Nuits", "appellation": "Gevrey-Chambertin", "classification": "Grand Cru" } ``` Only include fields you want to update. All fields are optional. **Response**: `200 OK` --- ### DELETE /api/wines/{wine_id} Delete a wine and all its history. **Response**: `204 No Content` --- ## Cellar Endpoints ### GET /api/cellar Get current cellar inventory (wines in stock). **Query Parameters**: - `skip`: Number to skip (default: 0) - `limit`: Maximum to return (default: 100) **Response**: `200 OK` Returns list of wines with quantity > 0. --- ### GET /api/cellar/summary Get cellar summary statistics. **Response**: `200 OK` ```json { "total_bottles": 42, "unique_wines": 15, "total_wines_tracked": 20, "by_vintage": { "2019": 12, "2020": 8 }, "by_country": { "France": 18, "Italy": 12 }, "by_grape_variety": { "Cabernet Sauvignon": 15, "Merlot": 10 } } ``` --- ## Transaction Endpoints ### GET /api/transactions List all transactions. **Query Parameters**: | Name | Type | Description | |------|------|-------------| | skip | integer | Number to skip | | limit | integer | Maximum to return | | transaction_type | string | Filter: "CHECK_IN" or "CHECK_OUT" | | wine_id | string | Filter by wine UUID | **Response**: `200 OK` ```json [ { "id": "uuid", "wine_id": "uuid", "transaction_type": "CHECK_IN", "quantity": 6, "notes": "...", "transaction_date": "2024-01-15T10:30:00Z", "created_at": "2024-01-15T10:30:00Z", "wine": { "id": "uuid", "name": "Wine Name", "vintage": 2019, "winery": "Winery Name" } } ] ``` --- ### GET /api/transactions/{transaction_id} Get a single transaction. **Response**: `200 OK` --- ## Search Endpoint ### GET /api/search Search wines by various criteria. **Query Parameters**: | Name | Type | Description | |------|------|-------------| | q | string | Full-text search (name, winery, region, sub-region, appellation, label text) | | vintage | integer | Vintage year | | grape | string | Grape variety (partial match) | | winery | string | Winery name (partial match) | | region | string | Region (partial match) | | country | string | Country (partial match) | | checked_in_after | datetime | Check-in date filter | | checked_in_before | datetime | Check-in date filter | | checked_out_after | datetime | Check-out date filter | | checked_out_before | datetime | Check-out date filter | | in_stock | boolean | Only in-stock wines | | skip | integer | Pagination offset | | limit | integer | Pagination limit | **Response**: `200 OK` Returns list of matching wines. --- ## Images ### GET /api/images/{filename} Serve stored label images. **Response**: Image file with appropriate content type. --- ## Export Endpoints ### GET /api/export/wines Export wine collection in various formats. **Query Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | format | string | No | Export format: `csv`, `xlsx`, `json`, `yaml` (default: `json`) | | in_stock | boolean | No | Only export in-stock wines | | country | string | No | Filter by country | | include_blends | boolean | No | Include grape blend summaries (default: true) | | include_scores | boolean | No | Include score summaries (default: true) | **CSV/XLSX columns**: `id`, `name`, `winery`, `vintage`, `grape_variety`, `region`, `country`, `alcohol_percentage`, `wine_type`, `price_tier`, `quantity`, `inventory_updated_at`, `grape_blend_summary`, `scores_summary`, `average_score`, `created_at`, `updated_at`, plus any custom field columns. Custom fields are expanded into individual columns (sorted alphabetically) rather than a single JSON blob. For example, if wines have custom fields "Purchase Price" and "Cellar Location", those appear as separate columns in the export. **Response**: File download (CSV/XLSX/YAML) or JSON body. --- ### GET /api/export/transactions Export transaction history in various formats. **Query Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | format | string | No | Export format: `csv`, `xlsx`, `json`, `yaml` (default: `json`) | | transaction_type | string | No | Filter: `CHECK_IN` or `CHECK_OUT` | | wine_id | string | No | Filter by wine UUID | | include_wine_details | boolean | No | Include wine name/vintage/winery (default: true) | **Response**: File download or JSON body. --- ## Import Endpoints ### POST /api/import/upload Upload a CSV or XLSX spreadsheet for import. **Content-Type**: `multipart/form-data` **Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | file | file | Yes | CSV or XLSX file (max 10 MB) | **Response**: `200 OK` ```json { "batch_id": "abc123", "filename": "wines.csv", "row_count": 100, "headers": ["Name", "Country", "Vintage", "Price"], "preview_rows": [...], "suggested_mapping": { "Name": "name", "Country": "country", "Vintage": "vintage", "Price": "skip" }, "mapping_source": "ai" } ``` --- ### POST /api/import/{batch_id}/mapping Set or update column mapping for an import batch. **Request Body**: ```json { "mapping": { "Name": "name", "Country": "country", "Vintage": "vintage", "Purchase Price": "custom:Purchase Price", "Notes Column": "skip" } } ``` Valid mapping targets: `name`, `winery`, `vintage`, `grape_variety`, `region`, `sub_region`, `appellation`, `country`, `alcohol_percentage`, `wine_type`, `classification`, `price_tier`, `quantity`, `notes`, `custom:`, or `skip`. **Response**: `200 OK` (updated batch info) --- ### POST /api/import/{batch_id}/process Process an import batch to create wine records. **Request Body** (optional): ```json { "skip_non_wine": true, "default_quantity": 1 } ``` **Response**: `200 OK` ```json { "batch_id": "abc123", "wines_created": 95, "rows_skipped": 5, "errors": [], "status": "completed" } ``` --- ## X-Wines Dataset Endpoints The X-Wines endpoints provide access to a reference database of 100K+ wines with community ratings from the [X-Wines dataset](https://github.com/rogerioxavier/X-Wines). ### GET /api/xwines/search Search X-Wines dataset for wine autocomplete. **Query Parameters**: | Name | Type | Required | Description | |------|------|----------|-------------| | q | string | Yes | Search query (min 2 characters) | | limit | integer | No | Maximum results (default: 10, max: 50) | | wine_type | string | No | Filter by wine type (Red, White, etc.) | | country | string | No | Filter by country code (FR, US, etc.) | **Response**: `200 OK` ```json { "results": [ { "id": 100062, "name": "Origem Merlot", "winery": "Casa Valduga", "wine_type": "Red", "country": "Brazil", "region": "Vale dos Vinhedos", "abv": 13.0, "avg_rating": 4.12, "rating_count": 21 } ], "total": 2 } ``` --- ### GET /api/xwines/wines/{wine_id} Get full details for a specific X-Wines wine. **Path Parameters**: - `wine_id`: Integer ID of the wine **Response**: `200 OK` ```json { "id": 100062, "name": "Origem Merlot", "wine_type": "Red", "elaborate": "Varietal/100%", "grapes": "['Merlot']", "harmonize": "['Beef', 'Lamb', 'Veal']", "abv": 13.0, "body": "Full-bodied", "acidity": "Medium", "country_code": "BR", "country": "Brazil", "region_id": 1002, "region_name": "Vale dos Vinhedos", "winery_id": 10014, "winery_name": "Casa Valduga", "website": "http://www.casavalduga.com.br", "vintages": "[2020, 2019, 2018, 2017]", "avg_rating": 4.12, "rating_count": 21 } ``` --- ### GET /api/xwines/stats Get X-Wines dataset statistics. **Response**: `200 OK` ```json { "wine_count": 100646, "rating_count": 21013536, "version": "full", "import_date": "2024-01-15T10:30:00", "source": "https://github.com/rogerioxavier/X-Wines" } ``` --- ### GET /api/xwines/types List distinct wine types in the dataset. **Response**: `200 OK` ```json ["Dessert/Port", "Fortified", "Red", "Rosé", "Sparkling", "White"] ``` --- ### GET /api/xwines/countries List countries with wine counts. **Response**: `200 OK` ```json [ {"code": "FR", "name": "France", "count": 25432}, {"code": "IT", "name": "Italy", "count": 18234}, {"code": "US", "name": "United States", "count": 15678} ] ``` --- ## Error Responses All endpoints may return these error responses: ### 400 Bad Request ```json { "detail": "Description of the error" } ``` ### 404 Not Found ```json { "detail": "Resource not found" } ``` ### 422 Unprocessable Entity ```json { "detail": [ { "loc": ["body", "field_name"], "msg": "Validation error message", "type": "error_type" } ] } ``` ### 500 Internal Server Error ```json { "detail": "Internal server error" } ```