Add current API doc

Amolith created

Change summary

API.md | 322 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 322 insertions(+)

Detailed changes

API.md 🔗

@@ -0,0 +1,322 @@
+# Cooked Customer API Spec
+
+This API lets a customer-owned integration log in, search and manage saved recipes, and manage the customer's shopping list.
+
+Base URL: `https://cooked.wiki`
+
+All JSON requests should use:
+
+```http
+Content-Type: application/json
+Accept: application/json
+```
+
+## Authentication
+
+The 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.
+
+Send the session cookie with every authenticated `/api/...` request:
+
+```http
+Cookie: <session-cookie-name>=<session-cookie-value>
+```
+
+There are two supported ways to obtain a session cookie.
+
+### Username/Password Login
+
+Use this flow only for accounts that have password login enabled.
+
+`POST /api/public/login`
+
+```json
+{
+  "username": "customer_username",
+  "password": "customer_password"
+}
+```
+
+Success:
+
+```json
+{
+  "message": "Authenticated successfully",
+  "username": "customer_username"
+}
+```
+
+Invalid login returns `401` with `code: "INVALID_AUTHENTICATION"`.
+
+On success, store the session cookie from the response and send it on future authenticated requests.
+
+### Browser Session Cookie
+
+Use this flow for social-login or passwordless accounts, such as accounts that log in with Google, Facebook, or Apple.
+
+1. Ask the user to log in normally at `https://cooked.wiki/login` in their browser.
+2. Ask the user to provide the Cooked session cookie for `https://cooked.wiki` through a trusted secret input.
+3. Store that cookie as a secret and send it as the `Cookie` header on API requests.
+
+The cookie is a bearer credential. Anyone with it can act as the logged-in user until it expires or is invalidated.
+
+### Expired Sessions
+
+If an authenticated API request returns `401`, discard the stored session cookie.
+
+If 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.
+
+## Recipes
+
+Recipe content is plain Markdown-style recipe text. Use `recipeId` values returned by list/search/create calls.
+
+| Action | Endpoint | Notes |
+| --- | --- | --- |
+| List recipes | `GET /api/user/{username}/recipes?page=1&page-count=30` | Returns saved recipe cards. |
+| Search recipes | `GET /api/user/{username}/recipes/search?q={query}&page=1` | Searches the user's saved cookbook. |
+| Get metadata | `GET /api/recipe/{recipeId}/metadata` | Title, image URLs, owner, edit permission. |
+| Get content | `GET /api/recipe/{recipeId}/content` | Recipe body and portions. |
+| Update content | `POST /api/recipe/{recipeId}/content` | Updates recipe body and portions. |
+| Delete recipe | `DELETE /api/recipe/{recipeId}` | Removes a saved recipe. |
+
+### Recipe Card
+
+List and search return recipe cards like:
+
+```json
+{
+  "id": "recipe-id",
+  "title": "Pasta with Tomato Sauce",
+  "thumbnail-url": "https://..."
+}
+```
+
+### Recipe Content
+
+`GET /api/recipe/{recipeId}/content` returns:
+
+```json
+{
+  "content": "# Pasta with Tomato Sauce\n\n- 200g pasta\n- 1 cup tomato sauce\n\n1. Boil the pasta.",
+  "portions": 2
+}
+```
+
+Update the recipe with:
+
+```json
+{
+  "description": "# Updated Recipe\n\n- ingredient\n\n1. step",
+  "portions": 4
+}
+```
+
+Success:
+
+```json
+{
+  "message": "Recipe updated",
+  "code": "RECIPE_UPDATED"
+}
+```
+
+### Create Recipe From Text
+
+Use this when the integration already has recipe text.
+
+1. Preview the recipe:
+
+`POST /api/new/from-text/preview`
+
+```json
+{
+  "recipe-name": "Pasta with Tomato Sauce",
+  "recipe-text": "Ingredients: 200g pasta, 1 cup tomato sauce. Steps: Boil pasta, warm sauce, combine."
+}
+```
+
+The response includes `title`, `markdown`, and `portions`.
+
+2. Save the preview:
+
+`POST /api/recipe/import/save`
+
+```json
+{
+  "title": "Pasta with Tomato Sauce",
+  "description": "# Pasta with Tomato Sauce\n\n- 200g pasta\n- 1 cup tomato sauce\n\n1. Boil the pasta.",
+  "portions": 2
+}
+```
+
+Success:
+
+```json
+{
+  "recipe-id": "new-recipe-id"
+}
+```
+
+### Create Recipe From URL
+
+Use this when the integration has a recipe URL.
+
+`POST /api/new`
+
+```json
+{
+  "url": "https://example.com/recipe"
+}
+```
+
+The response may include either a saved `recipe-id` or an import draft `extraction-id`.
+
+When an `extraction-id` is returned:
+
+1. Read the draft with `GET /api/recipe/{extractionId}/metadata` and `GET /api/recipe/{extractionId}/content`.
+2. Save it with `POST /api/extract/{extractionId}/save`.
+
+Save request:
+
+```json
+{
+  "description": "# Imported Recipe\n\n- ingredient\n\n1. step",
+  "portions": 4
+}
+```
+
+Success:
+
+```json
+{
+  "recipe-id": "new-recipe-id"
+}
+```
+
+## Shopping List
+
+The 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.
+
+| Action | Endpoint | Notes |
+| --- | --- | --- |
+| Add ingredients | `PUT /api/user/shopping-list` | Adds one or more ingredients to the logged-in user's list. |
+| Get list | `GET /api/user/{username}/shopping-list` | Returns aisles and product groups. |
+| Update item | `PUT /api/user/{username}/shopping-list/product-groups/{productGroupId}` | Updates name, quantity, aisle, or selected state. |
+| Mark selected | `PUT /api/user/{username}/shopping-list/product-groups/selection` | Replaces the complete selected/purchased set. |
+| Remove items | `DELETE /api/user/{username}/shopping-list/product-groups` | Removes product groups by ID. |
+| Clear list | `DELETE /api/user/{username}/shopping-list` | Clears all shopping-list items. |
+
+### Add Ingredients
+
+```json
+{
+  "ingredients": "200g pasta\n1 cup tomato sauce\nParmesan cheese",
+  "recipe-id": "optional-recipe-id"
+}
+```
+
+`recipe-id` is optional and may be a saved recipe ID or an import draft ID.
+
+Success:
+
+```json
+{
+  "success": true,
+  "added-count": 3,
+  "ingredients": [
+    "200g pasta",
+    "1 cup tomato sauce",
+    "Parmesan cheese"
+  ]
+}
+```
+
+### Shopping List Response
+
+```json
+{
+  "success": true,
+  "username": "customer_username",
+  "shopping-list": {
+    "aisles": [
+      {
+        "aisle-id": "pantry",
+        "aisle-name": "Pantry",
+        "product-groups": [
+          {
+            "id": "product-group-id",
+            "name": "Pasta",
+            "quantity": "200g",
+            "selected": false
+          }
+        ]
+      }
+    ]
+  },
+  "recipes": [
+    "recipe-id"
+  ]
+}
+```
+
+### Update Shopping List Item
+
+Send all editable fields:
+
+```json
+{
+  "name": "Pasta",
+  "quantity": "250g",
+  "aisle-id": "pantry",
+  "selected": false
+}
+```
+
+Success:
+
+```json
+{
+  "success": true
+}
+```
+
+### Mark Purchased Items
+
+`selected` is the full list of product group IDs that should be marked selected/purchased. Items not included are marked unselected.
+
+```json
+{
+  "selected": [
+    "product-group-id-1",
+    "product-group-id-2"
+  ]
+}
+```
+
+### Remove Shopping List Items
+
+```json
+{
+  "ids": [
+    "product-group-id-1",
+    "product-group-id-2"
+  ]
+}
+```
+
+## Common Errors
+
+| Status | Meaning |
+| --- | --- |
+| `400` | Invalid or missing request data. |
+| `401` | Not logged in or invalid credentials. |
+| `403` | Logged in but not allowed to edit this resource. |
+| `404` | Recipe, draft, or shopping-list item not found. |
+
+Example:
+
+```json
+{
+  "message": "Recipe not found",
+  "code": "RECIPE_NOT_FOUND"
+}
+```