{"openapi":"3.1.0","info":{"title":"Kreitzer Family Recipes — Agent API","version":"1.0.0","description":"Bearer-token API for external AI agents. Tokens are minted at /settings/tokens and carry the minter's owner permissions."},"servers":[{"url":"https://recipes.kreitzer.dev"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rkd_<64-hex>","description":"Long-lived API token. Keep secret — leaking grants owner-equivalent access."}},"schemas":{"Recipe":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"content":{"type":"string","description":"Full recipe markdown."},"author":{"type":"string"},"authorUid":{"type":"string"},"visibility":{"type":"string","enum":["public","private"]},"imagePath":{"type":"string","nullable":true,"description":"Public hero-image URL, or null. Set via /api/recipes/{id}/image."},"createdAt":{"type":"string","format":"date-time","nullable":true},"updatedAt":{"type":"string","format":"date-time","nullable":true},"promotedAt":{"type":"string","format":"date-time","nullable":true}}},"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}},"security":[{"BearerAuth":[]}],"paths":{"/api/agent/recipes":{"get":{"summary":"List recipes","description":"Search/list recipes. Default scope is \"all\" for owners (public + all users' recipes) or \"mine\" for non-owners.","parameters":[{"name":"scope","in":"query","schema":{"type":"string","enum":["mine","public","all"]},"description":"\"mine\" = your recipes only. \"public\" = public recipes only. \"all\" = both (owner only)."},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Substring match across title + summary + content."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"recipes":{"type":"array","items":{"$ref":"#/components/schemas/Recipe"}}}}}}}}},"post":{"summary":"Create a recipe (private)","description":"Creates a recipe owned by the token's user with visibility: \"private\". Use POST /recipes/{id}/publish to make it public (owner only).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":500},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"content":{"type":"string","maxLength":200000,"description":"Markdown."},"author":{"type":"string","description":"Display name. Defaults to token user."}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"recipe":{"$ref":"#/components/schemas/Recipe"}}}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Content too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agent/recipes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Read one recipe","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"recipe":{"$ref":"#/components/schemas/Recipe"}}}}}},"404":{"description":"Not found or private without permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a recipe (author or owner)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"category":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"content":{"type":"string","maxLength":200000}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"recipe":{"$ref":"#/components/schemas/Recipe"}}}}}},"404":{"description":"Not found or not yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a recipe (author or owner)","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"404":{"description":"Not found or not yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/recipes/{id}/image":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Set or replace a recipe's hero image (author or owner)","description":"Two ways to upload:\n\n1. multipart/form-data with a `file` field — direct binary upload, max 5 MB, image/* only.\n2. application/json `{ \"url\": \"https://...\" }` — server fetches the URL, validates it as an image, and re-hosts it on Vercel Blob.\n\nReturns the new public URL. Replaces any prior image (the previous blob is best-effort cleaned up).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}},"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"imagePath":{"type":"string","format":"uri"}}}}}},"413":{"description":"Image too large (>5 MB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Unsupported image type or wrong content-type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove a recipe's hero image","responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/api/agent/recipes/{id}/publish":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Publish a private recipe to the public site (owner only)","responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"type":"object","properties":{"recipe":{"$ref":"#/components/schemas/Recipe"}}}}}},"403":{"description":"Owner role required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Unpublish a public recipe back to private (owner only)","responses":{"200":{"description":"Unpublished","content":{"application/json":{"schema":{"type":"object","properties":{"recipe":{"$ref":"#/components/schemas/Recipe"}}}}}},"403":{"description":"Owner role required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}