Public REST API¶
Plain JSON routes under /api/v1 for your own scripts and automations, as an alternative to MCP. It's pull-based: to be told the moment something happens instead of polling, use webhooks. Off by default.
These routes (/api/v1/cook-diary, /api/v1/shopping, and the pantry stock route) are separate from the NutriTrace federation routes (GET /api/v1/recipes, GET /api/v1/pantry). Federation is a fixed contract for a sister Trace app and is always on; the routes here are for personal automation and sit behind the switches below.
Enable¶
Authentication¶
Create a token under Settings, API Tokens (admin, multi-user mode) and send it as a bearer token:
The same mcp:read and mcp:write scopes cover both MCP and these routes: mcp:read reads through either, mcp:write writes through either. There's no separate REST scope.
Read routes¶
Need mcp:read.
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/cook-diary?date_from=&date_to=&kind=&limit= |
Cook Diary entries, newest first. Optionally limited to a date range (YYYY-MM-DD) or a kind (cooked or planned). |
| GET | /api/v1/shopping?include_checked= |
The shopping list. Unchecked items only unless include_checked=true. |
Write routes¶
Need mcp:write and PUBLIC_API_WRITE_ENABLED=1.
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | /api/v1/cook-diary |
{ recipe_id, date?, servings?, notes?, meal_type?, rating? } |
Logs that you cooked a recipe. Fires the meal.cooked webhook. |
| PATCH | /api/v1/shopping/:id/check |
{ checked } |
Checks or unchecks an item. Checking the last one fires shopping_list.completed. |
| PATCH | /api/v1/pantry/:id/stock |
{ in_stock?, quantity? } |
Updates an existing pantry item's stock or quantity. Doesn't create items. Going out of stock fires pantry.out_of_stock. |
Creating recipes or pantry items, adding shopping items, and deleting entries aren't exposed here. Those are available through MCP's destructive tier, which needs MCP_DESTROY_ENABLED=1, the mcp:destroy scope, and confirm: true on every call.
Sister apps: the shopping scope¶
A token with the shopping scope gets its own version of /api/v1/shopping. It's always on, needs no PUBLIC_API_* or MCP switch, and reaches nothing but the token owner's shopping list. NoteTrace uses it to send items and show the list. A token without shopping sees the routes above as normal.
| Method | Path | Body | Returns |
|---|---|---|---|
| GET | /api/v1/shopping?include_checked= |
{ count, items }, sorted like the Shopping page. Checked items are included unless include_checked=false. |
|
| POST | /api/v1/shopping |
{ items: [{ name, quantity?, unit?, aisle? }] } or a single item |
201 { added, skipped }. Names are title-cased like the app, a name matching a pantry item takes its aisle, and an item already on the list unchecked is skipped. Up to 200 at a time. |
| PATCH | /api/v1/shopping/:id/check |
{ checked } |
{ ok, item_id, name, checked } |
| DELETE | /api/v1/shopping/checked |
{ removed }: clears checked items. |
Limits and errors¶
Each token gets 60 requests a minute (API_RATE_LIMIT_PER_MIN to change it). Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 also carries Retry-After.
A bad request returns 400 with { "error": "..." }. A missing or invalid token returns 401, and a token without the needed scope returns 403. A route whose switch is off returns 404.
Examples¶
# Recent Cook Diary entries
curl -H "Authorization: Bearer ct_pat_..." \
https://cooktrace.example.com/api/v1/cook-diary
# Log a cook
curl -X POST -H "Authorization: Bearer ct_pat_..." -H "Content-Type: application/json" \
-d '{"recipe_id": 42, "servings": 4}' \
https://cooktrace.example.com/api/v1/cook-diary
# Mark a pantry item out of stock
curl -X PATCH -H "Authorization: Bearer ct_pat_..." -H "Content-Type: application/json" \
-d '{"in_stock": false}' \
https://cooktrace.example.com/api/v1/pantry/17/stock