-
Notifications
You must be signed in to change notification settings - Fork 42
Rest API
Koffan provides a REST API for programmatic access to shopping lists. This is especially useful if you want to migrate from another service to Koffan.
Tip: Share this documentation with an AI assistant (like ChatGPT or Claude) and ask it to help you migrate your data. The AI can read your export file and generate the right API calls to import everything into Koffan.
-
Base URL:
/api/v1 - Authentication: Bearer token
-
Content-Type:
application/json
Quick start: Use the Batch endpoint to create a complete shopping list with sections and items in a single request!
The API is disabled by default. To enable it, set the API_TOKEN environment variable:
API_TOKEN=your-secret-tokenSecurity note: The API has no rate limiting. Use a strong, random token and keep it secret.
All requests require a Bearer token in the Authorization header:
Authorization: Bearer your-secret-token
| Error Code | Status | Description |
|---|---|---|
api_disabled |
503 | API_TOKEN not configured |
missing_token |
401 | No Authorization header |
invalid_format |
401 | Wrong format (not "Bearer token") |
invalid_token |
401 | Token doesn't match API_TOKEN |
GET /api/v1/lists
Response:
{
"lists": [
{
"id": 1,
"name": "Shopping",
"icon": "🛒",
"order": 1,
"stats": {
"total_items": 10,
"completed_items": 3
}
}
]
}GET /api/v1/lists/:id
POST /api/v1/lists
Request:
{
"name": "Weekly Shopping",
"icon": "cart"
}| Field | Type | Required | Max Length |
|---|---|---|---|
| name | string | Yes | 100 |
| icon | string | No | 20 (emoji or alias) |
Available icons:
You can use either emoji directly or string aliases:
| Icon | Emoji | String aliases |
|---|---|---|
| 🛒 | "🛒" |
"cart", "shopping"
|
| 🏠 | "🏠" |
"home", "house"
|
| 🎁 | "🎁" |
"gift", "present"
|
| 🎄 | "🎄" |
"christmas", "xmas"
|
| 🎂 | "🎂" |
"birthday", "cake"
|
| 🍕 | "🍕" |
"food", "pizza"
|
| 🥗 | "🥗" |
"salad", "healthy"
|
| 💊 | "💊" |
"medicine", "health", "pills"
|
| 🐕 | "🐕" |
"pet", "pets", "dog"
|
| 🧹 | "🧹" |
"cleaning", "clean"
|
| 📦 | "📦" |
"package", "packages", "box"
|
"✈️" |
"travel", "trip", "flight"
|
|
| 🏋️ | "🏋️" |
"fitness", "gym", "workout"
|
| 📚 | "📚" |
"books", "book", "reading"
|
| 🛠️ | "🛠️" |
"tools", "tool"
|
| 💼 | "💼" |
"work", "office", "business"
|
PUT /api/v1/lists/:id
Request:
{
"name": "New Name",
"icon": "🏠"
}DELETE /api/v1/lists/:id
Response: 204 No Content
GET /api/v1/lists/:id/sections
Response:
{
"sections": [
{
"id": 1,
"list_id": 1,
"name": "Produce",
"order": 1,
"items": []
}
]
}POST /api/v1/lists/:id/move-up
POST /api/v1/lists/:id/move-down
GET /api/v1/sections/:id
POST /api/v1/sections
Request:
{
"list_id": 1,
"name": "Produce"
}| Field | Type | Required | Max Length |
|---|---|---|---|
| list_id | int | Yes | - |
| name | string | Yes | 100 |
PUT /api/v1/sections/:id
Request:
{
"name": "Vegetables"
}DELETE /api/v1/sections/:id
GET /api/v1/sections/:id/items
Response:
{
"items": [
{
"id": 1,
"section_id": 1,
"name": "Apples",
"description": "Red apples",
"quantity": 2,
"completed": false,
"uncertain": false,
"order": 1
}
]
}POST /api/v1/sections/:id/move-up
POST /api/v1/sections/:id/move-down
GET /api/v1/items/:id
POST /api/v1/items
Request:
{
"section_id": 1,
"name": "Apples",
"description": "Red apples",
"quantity": 2
}| Field | Type | Required | Max Length |
|---|---|---|---|
| section_id | int | Yes | - |
| name | string | Yes | 200 |
| description | string | No | 500 |
| quantity | int | No | - (default: 0) |
PUT /api/v1/items/:id
Request:
{
"name": "Green Apples",
"description": "Granny Smith",
"quantity": 3,
"completed": true,
"uncertain": false
}Note: All fields are optional. Only provided fields will be updated. If
quantityis not provided, the existing value is preserved.
DELETE /api/v1/items/:id
POST /api/v1/items/:id/toggle
POST /api/v1/items/:id/uncertain
POST /api/v1/items/:id/move
Request:
{
"section_id": 2
}POST /api/v1/items/:id/move-up
POST /api/v1/items/:id/move-down
The most powerful endpoint! Create complete shopping lists with sections and items in a single API call.
POST /api/v1/batch
- One request - Create a list, multiple sections, and dozens of items at once
- Atomic - All-or-nothing transaction: everything succeeds or nothing changes
- Efficient - No need to chain multiple API calls and track IDs
- Flexible - Three modes: create new list, add to existing list, or add to existing section
Create a complete shopping list structure from scratch:
{
"list": {
"name": "Weekly Shopping",
"icon": "🛒",
"sections": [
{
"name": "Produce",
"items": [
{"name": "Apples", "description": "Red", "quantity": 2},
{"name": "Bananas", "quantity": 6}
]
},
{
"name": "Dairy",
"items": [
{"name": "Milk", "description": "2L", "quantity": 1}
]
}
]
}
}Expand an existing list with new sections and items (use list_id from a previous response):
{
"list_id": 1,
"sections": [
{
"name": "Frozen",
"items": [
{"name": "Ice Cream"}
]
}
]
}Quickly add multiple items to a section (use section_id from a previous response):
{
"section_id": 1,
"items": [
{"name": "Oranges", "description": "Fresh", "quantity": 4},
{"name": "Grapes", "quantity": 1}
]
}Response (contains only what was created):
{
"list": { ... }, // only in Variant 1
"sections": [ ... ], // in Variant 1 and 2
"items": [ ... ] // always present
}History entries are used for autocomplete suggestions when adding items.
GET /api/v1/history
Response:
{
"items": [
{
"id": 1,
"name": "Apples",
"section_id": 1,
"usage_count": 5
}
]
}POST /api/v1/history
Request:
{
"name": "Oranges",
"section_id": 1
}| Field | Type | Required | Max Length |
|---|---|---|---|
| name | string | Yes | 200 |
| section_id | int | No | - |
DELETE /api/v1/history/:id
POST /api/v1/history/batch-delete
Request:
{
"ids": [1, 2, 3]
}Response:
{
"deleted": 3
}All errors follow this format:
{
"error": "error_code",
"message": "Human readable message"
}| Error Code | Description |
|---|---|
invalid_json |
Failed to parse request body |
validation_error |
Validation constraint violated |
not_found |
Resource doesn't exist |
db_error |
Database operation failed |
create_failed |
Failed to create resource |
update_failed |
Failed to update resource |
delete_failed |
Failed to delete resource |
move_failed |
Reordering operation failed |
invalid_id |
ID parameter is not valid |
| Status | Description |
|---|---|
| 200 | Success (GET, PUT, POST actions) |
| 201 | Created (POST creating new resource) |
| 204 | No Content (DELETE) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (auth failed) |
| 404 | Not Found |
| 503 | Service Unavailable (API disabled) |
| 500 | Internal Server Error |
# Set token
TOKEN="Bearer your-secret-token"
# Create a list with sections and items (with quantities)
curl -X POST http://localhost:3000/api/v1/batch \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"list": {
"name": "Grocery",
"icon": "🛒",
"sections": [
{
"name": "Fruits",
"items": [
{"name": "Apples", "quantity": 4},
{"name": "Bananas", "quantity": 6}
]
}
]
}
}'
# Toggle item as completed
curl -X POST http://localhost:3000/api/v1/items/1/toggle \
-H "Authorization: $TOKEN"
# Get all lists
curl http://localhost:3000/api/v1/lists \
-H "Authorization: $TOKEN"