{"openapi":"3.1.0","info":{"title":"Shelly Restaurant Public API","version":"1.0.0","summary":"Read only public API for Shelly Restaurant, the immersive dining experience in Caulfield, Melbourne.","description":"The public, unauthenticated API behind shellyrestaurant.com.au. It serves live availability for the dining experiences, published guest reviews, and a contact form — the same data the booking pages render, so it reflects exactly what a guest sees. No API key is required and there is no write access to reservations; bookings are made through the experience pages or by phone. Rate limited to 60 requests per minute per IP; the response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. An MCP (Model Context Protocol) server wrapping the same data is available at /api/mcp, discoverable at /.well-known/mcp. Human documentation lives at /developers; the agent index at /for-agents.","contact":{"name":"Shelly — technical contact","email":"info@shellyrestaurant.com.au","url":"https://www.shellyrestaurant.com.au/developers"},"termsOfService":"https://www.shellyrestaurant.com.au/privacy-policy"},"servers":[{"url":"https://www.shellyrestaurant.com.au","description":"Production"}],"tags":[{"name":"availability","description":"Which nights each experience runs and what is sold out."},{"name":"reviews","description":"Published Google reviews, verbatim."},{"name":"contact","description":"Reach the restaurant."},{"name":"status","description":"Service health."},{"name":"discovery","description":"Machine readable indexes of this API and site."},{"name":"mcp","description":"Model Context Protocol endpoint (JSON-RPC 2.0 over Streamable HTTP)."}],"paths":{"/api":{"get":{"operationId":"getApiIndex","tags":["discovery"],"summary":"API directory","description":"Machine readable index of the public API: where the OpenAPI document, the documentation, the MCP server, and each public endpoint live. Not rate limited.","responses":{"200":{"description":"The API directory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/api/experience-dates":{"get":{"operationId":"listExperienceDates","tags":["availability"],"summary":"Bookable nights for one experience","description":"Every night the experience runs inside the live booking window (currently 60 days), with the seating labels for each night, which seatings are sold out, and — once a night is nearly full — how many seats remain across the whole night. Backed by the same data the on-site booking forms use. Availability changes during the day, so do not cache beyond the response's Cache-Control. Rate limited to 60 requests per minute per IP; the response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.","parameters":[{"name":"slug","in":"query","required":true,"description":"Which experience to list nights for.","schema":{"type":"string","enum":["4-seasons","trust-rony","four-elements","kids"]}}],"responses":{"200":{"description":"The bookable nights, soonest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperienceDates"}}}},"400":{"description":"The slug query parameter is missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperienceDatesError"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/reviews-list":{"get":{"operationId":"listReviews","tags":["reviews"],"summary":"Published guest reviews","description":"Published Google reviews for Shelly and the sister restaurant Shelanous, verbatim, paginated. Filter by venue, rating, or free text; sort by date or rating. Rate limited to 60 requests per minute per IP; the response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.","parameters":[{"name":"page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1,"maximum":500,"default":1}},{"name":"per_page","in":"query","required":false,"description":"Reviews per page.","schema":{"type":"integer","minimum":1,"maximum":50,"default":18}},{"name":"rating","in":"query","required":false,"description":"Only reviews with exactly this star rating.","schema":{"type":"integer","minimum":1,"maximum":5}},{"name":"rating_lte","in":"query","required":false,"description":"Only reviews with at most this star rating.","schema":{"type":"integer","minimum":1,"maximum":5}},{"name":"venue","in":"query","required":false,"description":"Restrict to one venue.","schema":{"type":"string","enum":["shelly","shelahem","shelanous"]}},{"name":"search","in":"query","required":false,"description":"Free text match on review text and author.","schema":{"type":"string","maxLength":120}},{"name":"sort","in":"query","required":false,"description":"Sort order.","schema":{"type":"string","enum":["newest","oldest","top"],"default":"newest"}}],"responses":{"200":{"description":"One page of reviews.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewsPage"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The reviews backend is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"}}}}}}},"/api/contact":{"post":{"operationId":"submitContactMessage","tags":["contact"],"summary":"Send a message to the restaurant","description":"Delivers a general enquiry to the team — the same channel as the form on /contact. Not for reservations: bookings are made through the experience pages or by phone. Rate limited to 60 requests per minute per IP; the response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactRequest"}}}},"responses":{"200":{"description":"The message was accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResult"}}}},"400":{"description":"A required field is missing or malformed; the error message names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The message could not be stored; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","tags":["status"],"summary":"Service health","description":"Aggregate health of the site's backing services. Suitable for uptime checks; returns 503 when degraded.","responses":{"200":{"description":"All backing services are configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"One or more backing services are not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/api/mcp":{"post":{"operationId":"mcpJsonRpc","tags":["mcp"],"summary":"Model Context Protocol endpoint","description":"JSON-RPC 2.0 over the MCP Streamable HTTP transport. Read only, unauthenticated, stateless: initialize, tools/list, tools/call, resources/list, resources/read. The manifest at /.well-known/mcp lists the tools. Requests must accept both application/json and text/event-stream. Notifications receive 202 with no body.","externalDocs":{"description":"Model Context Protocol specification","url":"https://modelcontextprotocol.io/specification"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}}},"responses":{"200":{"description":"The JSON-RPC response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"A JSON-RPC notification was accepted; no body."},"400":{"description":"The body is not a valid JSON-RPC 2.0 message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}}},"components":{"schemas":{"SimpleError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human readable description of what went wrong."}}},"ErrorDetail":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine readable error code, e.g. not_found or missing_parameter."},"message":{"type":"string","description":"Human readable description of what went wrong."},"hint":{"type":"string","description":"How to resolve the error: the parameter to add, or where the requested resource actually lives."}}},"StructuredError":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}}},"ApiIndex":{"type":"object","required":["name","openapi","documentation","endpoints"],"properties":{"name":{"type":"string"},"openapi":{"type":"string","description":"Path of this OpenAPI document."},"documentation":{"type":"string","description":"Human documentation (/developers)."},"agents":{"type":"string","description":"Agent oriented index (/for-agents)."},"llms":{"type":"string","description":"Path of llms.txt."},"mcp":{"type":"object","properties":{"endpoint":{"type":"string"},"manifest":{"type":"string"}}},"endpoints":{"type":"array","items":{"type":"object","required":["method","path","description"],"properties":{"method":{"type":"string"},"path":{"type":"string"},"description":{"type":"string"}}}}}},"ExperienceDates":{"type":"object","required":["dates"],"properties":{"dates":{"type":"array","description":"Bookable nights, soonest first.","items":{"$ref":"#/components/schemas/ExperienceNight"}}}},"ExperienceNight":{"type":"object","required":["iso","seatings","soldOut"],"properties":{"iso":{"type":"string","format":"date","description":"The night, as a Melbourne local date (YYYY-MM-DD)."},"seatings":{"type":"array","items":{"type":"string"},"description":"Seating labels for the night, e.g. \"5:30 PM\". Empty when the experience has one fixed seating."},"soldOut":{"type":"array","items":{"type":"string"},"description":"The subset of seatings with no seats left."},"nightLeft":{"type":["integer","null"],"description":"Seats remaining across the whole night, present only once the night is nearly full; null otherwise."}}},"ExperienceDatesError":{"type":"object","required":["dates","error"],"properties":{"dates":{"type":"null","description":"Always null on error (legacy shape)."},"error":{"$ref":"#/components/schemas/ErrorDetail"}}},"ReviewsPage":{"type":"object","required":["reviews","total","page","perPage","totalPages"],"properties":{"reviews":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"total":{"type":"integer"},"page":{"type":"integer"},"perPage":{"type":"integer"},"totalPages":{"type":"integer"}}},"Review":{"type":"object","required":["id","review_id","venue"],"properties":{"id":{"type":"integer"},"review_id":{"type":"string"},"author":{"type":["string","null"]},"rating":{"type":["integer","null"],"minimum":1,"maximum":5},"text":{"type":["string","null"]},"date":{"type":["string","null"],"description":"ISO timestamp the review was posted."},"dateFormatted":{"type":["string","null"],"description":"Human formatted date."},"reply":{"type":["string","null"],"description":"The restaurant's public reply, when there is one."},"venue":{"type":"string","enum":["shelly","shelahem","shelanous"]}}},"ContactRequest":{"type":"object","required":["name","email","phone","message"],"properties":{"name":{"type":"string","maxLength":120},"email":{"type":"string","format":"email"},"phone":{"type":"string","maxLength":40},"subject":{"type":"string","maxLength":200},"message":{"type":"string"}}},"ContactResult":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}},"Health":{"type":"object","required":["status","version","timestamp"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"version":{"type":"string","description":"Deployed git commit (short SHA)."},"timestamp":{"type":"string","format":"date-time"}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer","null"]},"method":{"type":"string","description":"initialize, tools/list, tools/call, resources/list, resources/read, ping, or a notification."},"params":{"type":"object"}}},"JsonRpcResponse":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer","null"]},"result":{"type":"object"},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}},"responses":{"RateLimited":{"description":"Too many requests from this IP. Wait for the window in X-RateLimit-Reset and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"}}}}}}}