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
# 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:
{
"access_token": "eyJ...",
"token_type": "bearer"
}
Using the Token
Include the token in the Authorization header for all requests:
curl -H "Authorization: Bearer <your-token>" \
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:
{
"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
{
"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 found400 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
[
{
"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
{
"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:
{
"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
{
"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
[
{
"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: |
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: |
transaction_type |
string |
No |
Filter: |
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
{
"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:
{
"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:<field_name>, 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):
{
"skip_non_wine": true,
"default_quantity": 1
}
Response: 200 OK
{
"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.
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
{
"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
{
"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
{
"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
["Dessert/Port", "Fortified", "Red", "Rosé", "Sparkling", "White"]
GET /api/xwines/countries
List countries with wine counts.
Response: 200 OK
[
{"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
{
"detail": "Description of the error"
}
404 Not Found
{
"detail": "Resource not found"
}
422 Unprocessable Entity
{
"detail": [
{
"loc": ["body", "field_name"],
"msg": "Validation error message",
"type": "error_type"
}
]
}
500 Internal Server Error
{
"detail": "Internal server error"
}