{"openapi":"3.1.0","info":{"title":"haute.food Lists API","version":"1.0.0","description":"Create, edit and export shareable restaurant lists. Exports are map files for Google My Maps (KML), spreadsheets (CSV), GeoJSON and GPX. Note: Google publishes no API for writing to the Saved lists inside the Google Maps app, so a list reaches a phone by importing the KML into Google My Maps or by opening each place link individually."},"servers":[{"url":"https://haute-food.vercel.app"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key (hf_…). Identifies the CALLER. Required to create or enumerate lists and to use the MCP endpoint. Issue one with `npm run keys:create`."},"editToken":{"type":"http","scheme":"bearer","description":"The edit token returned when a list is created. Authorises changes to THAT ONE LIST. Lists have no owner account, so the token is the whole per-list authorisation model. It is not a substitute for an API key, and an API key is not a substitute for it."}},"schemas":{"Place":{"type":"object","properties":{"id":{"type":"string","description":"Item id, used to update, remove or reorder"},"position":{"type":"integer"},"note":{"type":["string","null"]},"restaurantId":{"type":"string"},"name":{"type":"string"},"address":{"type":"string"},"cuisine":{"type":["string","null"]},"michelinStars":{"type":["integer","null"]},"priceRange":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"website":{"type":["string","null"]},"googlePlaceId":{"type":["string","null"]},"googleRating":{"type":["number","null"]},"googleMapsUrl":{"type":"string","description":"Opens this exact place in Google Maps, where it can be saved in one tap"},"appleMapsUrl":{"type":"string"}}},"List":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"authorName":{"type":["string","null"]},"city":{"type":["string","null"]},"isPublic":{"type":"boolean"},"placeCount":{"type":"integer"},"url":{"type":"string"},"places":{"type":"array","items":{"$ref":"#/components/schemas/Place"}},"exports":{"type":"array","items":{"type":"object","properties":{"format":{"type":"string","enum":["kml","csv","geojson","gpx"]},"label":{"type":"string"},"description":{"type":"string"},"url":{"type":"string"}}}}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}},"paths":{"/api/v1/lists":{"get":{"operationId":"browseLists","summary":"Browse public lists","description":"Requires an API key: enumerating the catalogue is the bulk-read path.","security":[{"apiKey":[]}],"parameters":[{"name":"query","in":"query","schema":{"type":"string"},"description":"Match list titles"},{"name":"citySlug","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Matching lists"}}},"post":{"operationId":"createList","summary":"Create a list","description":"Requires an API key. Returns the list plus an editToken, which authorises later changes to that list. The token is shown only once and cannot be recovered — store it.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":["string","null"],"maxLength":2000},"authorName":{"type":["string","null"],"maxLength":100},"citySlug":{"type":["string","null"]},"isPublic":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"The created list and its edit token"}}}},"/api/v1/lists/{slug}":{"get":{"operationId":"getList","summary":"Get a list and everything on it","description":"Public lists are readable without a key: a shared link has to work for whoever receives it. A private list requires its edit token. This exemption covers a SINGLE list by slug only — it does not extend to enumeration, which requires an API key.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The list","content":{"application/json":{"schema":{"type":"object","properties":{"list":{"$ref":"#/components/schemas/List"}}}}}},"404":{"description":"No such list"}}},"patch":{"operationId":"updateList","summary":"Update a list","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"authorName":{"type":["string","null"]},"citySlug":{"type":["string","null"]},"isPublic":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The updated list"}}},"delete":{"operationId":"deleteList","summary":"Delete a list","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"}}}},"/api/v1/lists/{slug}/places":{"post":{"operationId":"addPlaceToList","summary":"Add a place to a list","description":"Give one of restaurantId, googlePlaceId or query. A free-text query is resolved against the haute.food database first and Google Places second, creating the record if needed.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"restaurantId":{"type":"string","format":"uuid"},"googlePlaceId":{"type":"string"},"query":{"type":"string","description":"Restaurant name, e.g. \"Bleecker Burger\""},"near":{"type":["string","null"],"description":"City to disambiguate, e.g. \"London\""},"note":{"type":["string","null"],"description":"Why it is on the list"}}}}}},"responses":{"200":{"description":"The updated list"}}}},"/api/v1/lists/{slug}/places/{itemId}":{"patch":{"operationId":"updatePlaceNote","summary":"Update the note on a place","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"The updated list"}}},"delete":{"operationId":"removePlaceFromList","summary":"Remove a place from a list","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated list"}}}},"/api/v1/lists/{slug}/reorder":{"post":{"operationId":"reorderList","summary":"Set the order of a list","description":"Pass every item id exactly once, in the desired order. Partial orderings are rejected.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemIds"],"properties":{"itemIds":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"The reordered list"}}}},"/api/v1/lists/{slug}/export/{format}":{"get":{"operationId":"exportList","summary":"Download a list as a map file","description":"Use kml for Google My Maps, gpx for offline apps such as Organic Maps. Add ?json=1 to get the document as a JSON string with warnings and import instructions instead of a file download. Public lists export without a key, because the download button on a shared page uses this route.","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"path","required":true,"schema":{"type":"string","enum":["kml","csv","geojson","gpx"]}},{"name":"json","in":"query","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The export file"}}}},"/api/v1/places/search":{"get":{"operationId":"searchPlaces","summary":"Search for restaurants to add to a list","description":"Without an API key or a valid list edit token, only the haute.food database is searched; Google Places is billable and is reserved for identified callers.","security":[],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string"}},{"name":"near","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20}}],"responses":{"200":{"description":"Matching places"}}}}}}