API.md

  1# Cooked Customer API Spec
  2
  3This API lets a customer-owned integration log in, search and manage saved recipes, and manage the customer's shopping list.
  4
  5Base URL: `https://cooked.wiki`
  6
  7All JSON requests should use:
  8
  9```http
 10Content-Type: application/json
 11Accept: application/json
 12```
 13
 14## Authentication
 15
 16The API uses a Cooked session cookie for authenticated requests. Treat this cookie like a password: store it only in a trusted secret store, never log it, and never ask users to paste it into public or untrusted chats.
 17
 18Send the session cookie with every authenticated `/api/...` request:
 19
 20```http
 21Cookie: <session-cookie-name>=<session-cookie-value>
 22```
 23
 24There are two supported ways to obtain a session cookie.
 25
 26### Username/Password Login
 27
 28Use this flow only for accounts that have password login enabled.
 29
 30`POST /api/public/login`
 31
 32```json
 33{
 34  "username": "customer_username",
 35  "password": "customer_password"
 36}
 37```
 38
 39Success:
 40
 41```json
 42{
 43  "message": "Authenticated successfully",
 44  "username": "customer_username"
 45}
 46```
 47
 48Invalid login returns `401` with `code: "INVALID_AUTHENTICATION"`.
 49
 50On success, store the session cookie from the response and send it on future authenticated requests.
 51
 52### Browser Session Cookie
 53
 54Use this flow for social-login or passwordless accounts, such as accounts that log in with Google, Facebook, or Apple.
 55
 561. Ask the user to log in normally at `https://cooked.wiki/login` in their browser.
 572. Ask the user to provide the Cooked session cookie for `https://cooked.wiki` through a trusted secret input.
 583. Store that cookie as a secret and send it as the `Cookie` header on API requests.
 59
 60The cookie is a bearer credential. Anyone with it can act as the logged-in user until it expires or is invalidated.
 61
 62### Expired Sessions
 63
 64If an authenticated API request returns `401`, discard the stored session cookie.
 65
 66If username/password credentials are configured, retry `POST /api/public/login` once and then retry the original request with the new cookie. If the account uses social login or no password is available, prompt the user to log in in the browser again and provide a fresh Cooked session cookie through a trusted secret input.
 67
 68## Recipes
 69
 70Recipe content is plain Markdown-style recipe text. Use `recipeId` values returned by list/search/create calls.
 71
 72| Action | Endpoint | Notes |
 73| --- | --- | --- |
 74| List recipes | `GET /api/user/{username}/recipes?page=1&page-count=30` | Returns saved recipe cards. |
 75| Search recipes | `GET /api/user/{username}/recipes/search?q={query}&page=1` | Searches the user's saved cookbook. |
 76| Get metadata | `GET /api/recipe/{recipeId}/metadata` | Title, image URLs, owner, edit permission. |
 77| Get content | `GET /api/recipe/{recipeId}/content` | Recipe body and portions. |
 78| Update content | `POST /api/recipe/{recipeId}/content` | Updates recipe body and portions. |
 79| Delete recipe | `DELETE /api/recipe/{recipeId}` | Removes a saved recipe. |
 80
 81### Recipe Card
 82
 83List and search return recipe cards like:
 84
 85```json
 86{
 87  "id": "recipe-id",
 88  "title": "Pasta with Tomato Sauce",
 89  "thumbnail-url": "https://..."
 90}
 91```
 92
 93### Recipe Content
 94
 95`GET /api/recipe/{recipeId}/content` returns:
 96
 97```json
 98{
 99  "content": "# Pasta with Tomato Sauce\n\n- 200g pasta\n- 1 cup tomato sauce\n\n1. Boil the pasta.",
100  "portions": 2
101}
102```
103
104Update the recipe with:
105
106```json
107{
108  "description": "# Updated Recipe\n\n- ingredient\n\n1. step",
109  "portions": 4
110}
111```
112
113Success:
114
115```json
116{
117  "message": "Recipe updated",
118  "code": "RECIPE_UPDATED"
119}
120```
121
122### Create Recipe From Text
123
124Use this when the integration already has recipe text.
125
1261. Preview the recipe:
127
128`POST /api/new/from-text/preview`
129
130```json
131{
132  "recipe-name": "Pasta with Tomato Sauce",
133  "recipe-text": "Ingredients: 200g pasta, 1 cup tomato sauce. Steps: Boil pasta, warm sauce, combine."
134}
135```
136
137The response includes `title`, `markdown`, and `portions`.
138
1392. Save the preview:
140
141`POST /api/recipe/import/save`
142
143```json
144{
145  "title": "Pasta with Tomato Sauce",
146  "description": "# Pasta with Tomato Sauce\n\n- 200g pasta\n- 1 cup tomato sauce\n\n1. Boil the pasta.",
147  "portions": 2
148}
149```
150
151Success:
152
153```json
154{
155  "recipe-id": "new-recipe-id"
156}
157```
158
159### Create Recipe From URL
160
161Use this when the integration has a recipe URL.
162
163`POST /api/new`
164
165```json
166{
167  "url": "https://example.com/recipe"
168}
169```
170
171The response may include either a saved `recipe-id` or an import draft `extraction-id`.
172
173When an `extraction-id` is returned:
174
1751. Read the draft with `GET /api/recipe/{extractionId}/metadata` and `GET /api/recipe/{extractionId}/content`.
1762. Save it with `POST /api/extract/{extractionId}/save`.
177
178Save request:
179
180```json
181{
182  "description": "# Imported Recipe\n\n- ingredient\n\n1. step",
183  "portions": 4
184}
185```
186
187Success:
188
189```json
190{
191  "recipe-id": "new-recipe-id"
192}
193```
194
195## Shopping List
196
197The API accepts plain-text ingredients and returns organized shopping-list product groups. Use product group IDs from `GET /api/user/{username}/shopping-list` for updates and removals.
198
199| Action | Endpoint | Notes |
200| --- | --- | --- |
201| Add ingredients | `PUT /api/user/shopping-list` | Adds one or more ingredients to the logged-in user's list. |
202| Get list | `GET /api/user/{username}/shopping-list` | Returns aisles and product groups. |
203| Update item | `PUT /api/user/{username}/shopping-list/product-groups/{productGroupId}` | Updates name, quantity, aisle, or selected state. |
204| Mark selected | `PUT /api/user/{username}/shopping-list/product-groups/selection` | Replaces the complete selected/purchased set. |
205| Remove items | `DELETE /api/user/{username}/shopping-list/product-groups` | Removes product groups by ID. |
206| Clear list | `DELETE /api/user/{username}/shopping-list` | Clears all shopping-list items. |
207
208### Add Ingredients
209
210```json
211{
212  "ingredients": "200g pasta\n1 cup tomato sauce\nParmesan cheese",
213  "recipe-id": "optional-recipe-id"
214}
215```
216
217`recipe-id` is optional and may be a saved recipe ID or an import draft ID.
218
219Success:
220
221```json
222{
223  "success": true,
224  "added-count": 3,
225  "ingredients": [
226    "200g pasta",
227    "1 cup tomato sauce",
228    "Parmesan cheese"
229  ]
230}
231```
232
233### Shopping List Response
234
235```json
236{
237  "success": true,
238  "username": "customer_username",
239  "shopping-list": {
240    "aisles": [
241      {
242        "aisle-id": "pantry",
243        "aisle-name": "Pantry",
244        "product-groups": [
245          {
246            "id": "product-group-id",
247            "name": "Pasta",
248            "quantity": "200g",
249            "selected": false
250          }
251        ]
252      }
253    ]
254  },
255  "recipes": [
256    "recipe-id"
257  ]
258}
259```
260
261### Update Shopping List Item
262
263Send all editable fields:
264
265```json
266{
267  "name": "Pasta",
268  "quantity": "250g",
269  "aisle-id": "pantry",
270  "selected": false
271}
272```
273
274Success:
275
276```json
277{
278  "success": true
279}
280```
281
282### Mark Purchased Items
283
284`selected` is the full list of product group IDs that should be marked selected/purchased. Items not included are marked unselected.
285
286```json
287{
288  "selected": [
289    "product-group-id-1",
290    "product-group-id-2"
291  ]
292}
293```
294
295### Remove Shopping List Items
296
297```json
298{
299  "ids": [
300    "product-group-id-1",
301    "product-group-id-2"
302  ]
303}
304```
305
306## Common Errors
307
308| Status | Meaning |
309| --- | --- |
310| `400` | Invalid or missing request data. |
311| `401` | Not logged in or invalid credentials. |
312| `403` | Logged in but not allowed to edit this resource. |
313| `404` | Recipe, draft, or shopping-list item not found. |
314
315Example:
316
317```json
318{
319  "message": "Recipe not found",
320  "code": "RECIPE_NOT_FOUND"
321}
322```