13 KiB
Costco Grocery List API Documentation
Base URL: http://localhost:5000/api
Table of Contents
- Authentication
- Grocery List Management
- User Management
- Admin Operations
- Role-Based Access Control
- Error Responses
Authentication
All authenticated endpoints require a JWT token in the Authorization header:
Authorization: Bearer <token>
POST /auth/register
Register a new user account.
Access: Public
Request Body:
{
"username": "string (lowercase)",
"password": "string",
"name": "string"
}
Response: 200 OK
{
"message": "User registered",
"user": {
"id": 1,
"username": "johndoe",
"name": "John Doe",
"role": "viewer"
}
}
Error: 400 Bad Request
{
"message": "Registration failed",
"error": "Error details"
}
POST /auth/login
Authenticate and receive a JWT token.
Access: Public
Request Body:
{
"username": "string (lowercase)",
"password": "string"
}
Response: 200 OK
{
"token": "jwt_token_string",
"username": "johndoe",
"role": "editor"
}
Errors:
401 Unauthorized- User not found or invalid credentials
{
"message": "User not found"
}
or
{
"message": "Invalid credentials"
}
Grocery List Management
GET /list
Retrieve all unbought grocery items.
Access: Authenticated (All roles)
Query Parameters: None
Response: 200 OK
[
{
"id": 1,
"item_name": "milk",
"quantity": 2,
"bought": false,
"item_image": "base64_encoded_string",
"image_mime_type": "image/jpeg",
"added_by_users": ["John Doe", "Jane Smith"],
"modified_on": "2026-01-03T12:00:00Z",
"item_type": "dairy",
"item_group": "milk",
"zone": "DAIRY"
}
]
Fields:
id- Unique item identifieritem_name- Name of grocery item (lowercase)quantity- Number of items neededbought- Purchase status (always false for this endpoint)item_image- Base64 encoded image (nullable)image_mime_type- MIME type of image (nullable)added_by_users- Array of user names who added/modified this itemmodified_on- Last modification timestampitem_type- Classification type (nullable)item_group- Classification group (nullable)zone- Store zone location (nullable)
GET /list/item-by-name
Retrieve a specific item by name (case-insensitive).
Access: Authenticated (All roles)
Query Parameters:
itemName(required) - Name of the item to search
Example: GET /list/item-by-name?itemName=milk
Response: 200 OK
{
"id": 1,
"item_name": "milk",
"quantity": 2,
"bought": false,
"item_image": "base64_encoded_string",
"image_mime_type": "image/jpeg"
}
Returns null if item not found.
GET /list/suggest
Get item name suggestions based on partial input.
Access: Authenticated (All roles)
Query Parameters:
query(required) - Partial item name to search
Example: GET /list/suggest?query=mil
Response: 200 OK
[
{ "item_name": "milk" },
{ "item_name": "almond milk" },
{ "item_name": "oat milk" }
]
Behavior:
- Case-insensitive partial matching (ILIKE)
- Searches all items in
grocery_list(bought and unbought) - Returns up to 10 suggestions
- Ordered by database query result
GET /list/recently-bought
Retrieve items bought within the last 24 hours.
Access: Authenticated (All roles)
Query Parameters: None
Response: 200 OK
[
{
"id": 5,
"item_name": "bread",
"quantity": 1,
"bought": true,
"item_image": "base64_encoded_string",
"image_mime_type": "image/jpeg",
"added_by_users": ["John Doe"],
"modified_on": "2026-01-03T10:30:00Z",
"item_type": "bakery",
"item_group": "bread",
"zone": "BAKERY"
}
]
Behavior:
- Returns items with
bought = true - Filters by
modified_onwithin last 24 hours - Includes classification data if available
GET /list/item/:id/classification
Get classification data for a specific item.
Access: Authenticated (All roles)
Path Parameters:
id(required) - Item ID
Example: GET /list/item/1/classification
Response: 200 OK
{
"item_type": "dairy",
"item_group": "milk",
"zone": "DAIRY",
"confidence": 1.0,
"source": "user"
}
Returns empty object {} if no classification exists.
Fields:
item_type- Classification type (e.g., "dairy", "produce", "meat")item_group- Sub-group within type (e.g., "milk", "cheese")zone- Store zone (e.g., "DAIRY", "PRODUCE", "MEAT")confidence- Classification confidence (0.0 to 1.0)source- Origin of classification ("user" or "auto")
POST /list/add
Add a new item or update quantity of existing item.
Access: Editor, Admin
Content-Type: multipart/form-data
Request Body:
itemName: string (required)
quantity: number (required)
image: file (optional) - Image file
Response: 200 OK
{
"message": "Item added/updated",
"addedBy": 1
}
Behavior:
- If item exists and is unbought: updates quantity
- If item exists and is bought: marks as unbought with new quantity
- If item doesn't exist: creates new item
- Adds record to
grocery_historytable - Processes and optimizes uploaded image (800x800px, JPEG 85% quality)
Image Processing:
- Accepted formats: JPEG, PNG, GIF, WebP
- Max file size: 5MB
- Output: 800x800px JPEG at 85% quality
- Stored as binary in database
POST /list/update-image
Update or add image to an existing item.
Access: Editor, Admin
Content-Type: multipart/form-data
Request Body:
id: number (required)
itemName: string (required)
quantity: number (required)
image: file (required) - Image file
Response: 200 OK
{
"message": "Image updated successfully"
}
Error: 400 Bad Request
{
"message": "No image provided"
}
POST /list/mark-bought
Mark an item as purchased.
Access: Editor, Admin
Request Body:
{
"id": 1
}
Response: 200 OK
{
"message": "Item marked bought"
}
Behavior:
- Sets
bought = true - Updates
modified_ontimestamp - Item moves to "Recently Bought" list
PUT /list/item/:id
Update item details and/or classification.
Access: Editor, Admin
Path Parameters:
id(required) - Item ID
Request Body:
{
"itemName": "string (optional)",
"quantity": 2,
"classification": {
"item_type": "dairy",
"item_group": "milk",
"zone": "DAIRY"
}
}
Response: 200 OK
{
"message": "Item updated successfully"
}
Errors:
400 Bad Request- Invalid classification values
{
"message": "Invalid item_type"
}
or
{
"message": "Invalid item_group for selected item_type"
}
or
{
"message": "Invalid zone"
}
Classification Validation:
item_type: Must be one of the valid types defined inbackend/constants/classifications.jsitem_group: Must be valid for the selecteditem_typezone: Must be one of the predefined store zones
Valid Zones:
- ENTRANCE, PRODUCE, MEAT, DELI, BAKERY, DAIRY, FROZEN, DRY_GOODS, BEVERAGES, SNACKS, HOUSEHOLD, HEALTH, CHECKOUT
User Management
GET /users/exists
Check if a username already exists.
Access: Public
Query Parameters:
username(required) - Username to check
Example: GET /users/exists?username=johndoe
Response: 200 OK
{
"exists": true
}
or
{
"exists": false
}
GET /users/test
Health check endpoint for user routes.
Access: Public
Response: 200 OK
{
"message": "User route is working"
}
Admin Operations
GET /admin/users
Retrieve all registered users.
Access: Admin only
Query Parameters: None
Response: 200 OK
[
{
"id": 1,
"username": "johndoe",
"name": "John Doe",
"role": "editor"
},
{
"id": 2,
"username": "janesmith",
"name": "Jane Smith",
"role": "viewer"
}
]
PUT /admin/users
Update a user's role.
Access: Admin only
Request Body:
{
"id": 1,
"role": "admin"
}
Valid Roles:
viewer- Read-only accesseditor- Can add/modify/buy itemsadmin- Full access including user management
Response: 200 OK
{
"message": "Role updated",
"id": 1,
"role": "admin"
}
Errors:
400 Bad Request- Invalid role
{
"error": "Invalid role"
}
404 Not Found- User not found
{
"error": "User not found"
}
500 Internal Server Error
{
"error": "Failed to update role"
}
DELETE /admin/users
Delete a user account.
Access: Admin only
Path Parameters:
id(required) - User ID
Example: DELETE /admin/users?id=5
Response: 200 OK
{
"message": "User deleted",
"id": 5
}
Errors:
404 Not Found
{
"error": "User not found"
}
500 Internal Server Error
{
"error": "Failed to delete user"
}
Role-Based Access Control
Roles and Permissions
| Role | View Lists | Add/Edit Items | Buy Items | User Management |
|---|---|---|---|---|
| viewer | ✅ | ❌ | ❌ | ❌ |
| editor | ✅ | ✅ | ✅ | ❌ |
| admin | ✅ | ✅ | ✅ | ✅ |
Protected Endpoints by Role
All Authenticated Users (viewer, editor, admin):
- GET /list
- GET /list/item-by-name
- GET /list/suggest
- GET /list/recently-bought
- GET /list/item/:id/classification
Editor and Admin Only:
- POST /list/add
- POST /list/update-image
- POST /list/mark-bought
- PUT /list/item/:id
Admin Only:
- GET /admin/users
- PUT /admin/users
- DELETE /admin/users
Error Responses
Standard Error Format
{
"message": "Error description",
"error": "Detailed error information (optional)"
}
Common HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
| 200 | OK | Successful request |
| 400 | Bad Request | Invalid input data, missing required fields |
| 401 | Unauthorized | Missing/invalid token, invalid credentials |
| 403 | Forbidden | Insufficient permissions for operation |
| 404 | Not Found | Resource doesn't exist |
| 500 | Internal Server Error | Server-side error |
Authentication Errors
Missing Token:
{
"message": "No token provided"
}
Invalid Token:
{
"message": "Invalid or expired token"
}
Insufficient Permissions:
{
"message": "Access denied"
}
Data Models
User
{
id: number
username: string (lowercase, unique)
password: string (bcrypt hashed)
name: string
role: "viewer" | "editor" | "admin"
}
Grocery Item
{
id: number
item_name: string (lowercase)
quantity: number
bought: boolean
item_image: Buffer | null (binary image data)
image_mime_type: string | null
added_by: number (user id)
modified_on: timestamp
}
Item Classification
{
id: number (references grocery_list.id)
item_type: string | null
item_group: string | null
zone: string | null
confidence: number (0.0 - 1.0)
source: "user" | "auto"
}
Grocery History
{
id: number
list_item_id: number (references grocery_list.id)
quantity: number
added_by: number (user id)
added_on: timestamp
}
Notes
Case Sensitivity
- Usernames are stored and compared in lowercase
- Item names are stored in lowercase
- All searches are case-insensitive (ILIKE in PostgreSQL)
Token Expiration
- JWT tokens expire after 1 year
- Include token expiration handling in client applications
Image Optimization
- Images are automatically processed:
- Resized to 800x800px (maintains aspect ratio)
- Converted to JPEG format
- Compressed to 85% quality
- Max upload size: 5MB
Database Transaction Handling
- Item additions/updates include history tracking
- Classification updates are atomic operations
CORS Configuration
- Allowed origins configured via
ALLOWED_ORIGINSenvironment variable - Supports both static origins and IP range patterns (e.g.,
192.168.*.*)
Frontend Integration Examples
Login Flow
const response = await axios.post('/api/auth/login', {
username: 'johndoe',
password: 'password123'
});
const { token, role, username } = response.data;
localStorage.setItem('token', token);
localStorage.setItem('role', role);
localStorage.setItem('username', username);
Authenticated Request
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:5000/api',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const items = await api.get('/list');
Adding Item with Image
const formData = new FormData();
formData.append('itemName', 'milk');
formData.append('quantity', 2);
formData.append('image', imageFile); // File object from input
await api.post('/list/add', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
Version Information
- API Version: 1.0
- Last Updated: January 3, 2026
- Backend Framework: Express 5.1.0
- Database: PostgreSQL 8.16.0
- Authentication: JWT (jsonwebtoken 9.0.2)