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 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

[
  {
    "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


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

{
  "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/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"
}