{"openapi":"3.1.0","info":{"title":"Planoora Public Booking API","version":"1.0.0","summary":"Search partners, list slot availability, and create a checkout URL. Agents never confirm payment.","description":"Public, unauthenticated REST for AI agents and integrations.\n\nUse this when a person wants to book a local service in Greece (nails, facial, hair, fitness) or a named Planoora partner such as Yamena.\n\nDefault search location is Thessaloniki when lat/lng and city are omitted.\n\ncreate_booking_intent does **not** hold the calendar. Give the user checkout_url; they log in if needed and pay in the Planoora app.\n\nUnclaimed /places listings are not bookable.","contact":{"name":"Planoora","email":"info@planoora.com","url":"https://www.planoora.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://www.planoora.com","description":"Production"}],"tags":[{"name":"Stores","description":"Partner discovery"},{"name":"Availability","description":"Open appointment slots for one calendar day"},{"name":"Booking intents","description":"Pre-filled checkout links (no calendar hold)"}],"paths":{"/api/v1/stores/search":{"get":{"operationId":"search_stores","tags":["Stores"],"summary":"Search bookable Planoora partners","description":"Find partners by store name (Yamena) or service text (nails, facial). Defaults to Thessaloniki, Greece if no coordinates are sent. Prefer this when the user did not name a slug.","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Free text: store name or service (nails, face treatment, Yamena)."},{"name":"city","in":"query","schema":{"type":"string","example":"thessaloniki"}},{"name":"lat","in":"query","schema":{"type":"number"}},{"name":"lng","in":"query","schema":{"type":"number"}},{"name":"when","in":"query","schema":{"type":"string","format":"date"},"description":"Optional YYYY-MM-DD preference (does not guarantee open slots)."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20,"default":8}}],"responses":{"200":{"description":"Matching bookable stores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreSearchResponse"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/stores/{slug}":{"get":{"operationId":"get_store","tags":["Stores"],"summary":"Get a bookable partner by slug","parameters":[{"$ref":"#/components/parameters/StoreSlug"}],"responses":{"200":{"description":"Store profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDetail"}}}},"404":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/stores/{slug}/services":{"get":{"operationId":"list_services","tags":["Stores"],"summary":"List active services for a partner","description":"Only booking_type=slot services with bookable_via_api=true can be used with get_availability in v1.","parameters":[{"$ref":"#/components/parameters/StoreSlug"}],"responses":{"200":{"description":"Service catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceListResponse"}}}},"404":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/stores/{slug}/availability":{"get":{"operationId":"get_availability","tags":["Availability"],"summary":"List open slots for one local calendar day","description":"Returns HH:MM times in the store timezone plus staff who can take the slot. Not a reservation. Staff-only timed appointments in v1 (no events, stays, or resource-only).","parameters":[{"$ref":"#/components/parameters/StoreSlug"},{"name":"service_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"description":"Store-local calendar day YYYY-MM-DD. One day per request."},{"name":"staff_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Optional. Omit to see every staff member who can take the service that day."}],"responses":{"200":{"description":"Open slots","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResponse"}}}},"400":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/booking-intents":{"post":{"operationId":"create_booking_intent","tags":["Booking intents"],"summary":"Create a pre-filled checkout URL","description":"Validates that the slot is still listed as open, then stores a 60-minute intent. Does not hold the calendar and does not charge. Return checkout_url to the user. Never claim the booking is confirmed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingIntentRequest"}}}},"responses":{"201":{"description":"Checkout URL ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingIntent"}}}},"400":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/booking-intents/{token}":{"get":{"operationId":"get_booking_intent","tags":["Booking intents"],"summary":"Load a checkout intent by token","description":"Used by app.planoora.com to pre-fill the cart. Public read of unexpired intents.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Intent snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingIntent"}}}},"404":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Structured error (RFC 9457)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"parameters":{"StoreSlug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"yamena-beauty"},"description":"URL slug from search_stores (store name slugified)."}},"schemas":{"Problem":{"type":"object","required":["type","title","status","detail","code"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"code":{"type":"string"},"hint":{"type":"string","description":"What the agent should do next"}}},"StoreSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string","example":"yamena-beauty"},"name":{"type":"string"},"type":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"rating":{"type":"number","nullable":true},"timezone":{"type":"string","nullable":true},"lat":{"type":"number","nullable":true},"lng":{"type":"number","nullable":true},"cover_image_url":{"type":"string","nullable":true},"booking_url":{"type":"string","format":"uri"},"seo_url":{"type":"string","format":"uri"}}},"StoreDetail":{"allOf":[{"$ref":"#/components/schemas/StoreSummary"},{"type":"object","properties":{"description":{"type":"string","nullable":true},"logo_url":{"type":"string","nullable":true},"business_hours":{"type":"object","nullable":true},"notice":{"type":"string"}}}]},"StoreSearchResponse":{"type":"object","properties":{"stores":{"type":"array","items":{"$ref":"#/components/schemas/StoreSummary"}},"default_location":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"lat":{"type":"number"},"lng":{"type":"number"}}},"query":{"type":"object"}}},"ServiceListResponse":{"type":"object","properties":{"store":{"$ref":"#/components/schemas/StoreSummary"},"services":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"duration_minutes":{"type":"integer","nullable":true},"price":{"type":"number","nullable":true},"description":{"type":"string","nullable":true},"booking_type":{"type":"string"},"bookable_via_api":{"type":"boolean"}}}}}},"AvailabilityResponse":{"type":"object","properties":{"store":{"$ref":"#/components/schemas/StoreSummary"},"service":{"type":"object"},"date":{"type":"string","format":"date"},"timezone":{"type":"string","nullable":true},"slots":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string","example":"10:00"},"start_time":{"type":"string","format":"date-time"},"end_time":{"type":"string","format":"date-time"},"staff":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true}}}}}}},"notice":{"type":"string"}}},"CreateBookingIntentRequest":{"type":"object","required":["slug","service_id","date","time"],"properties":{"slug":{"type":"string","example":"yamena-beauty"},"service_id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date"},"time":{"type":"string","example":"10:00","description":"Store-local HH:MM from get_availability"},"staff_id":{"type":"string","format":"uuid","description":"Optional. Omit to pick any staff free at that time."}}},"BookingIntent":{"type":"object","properties":{"token":{"type":"string"},"checkout_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time"},"summary":{"type":"string"},"notice":{"type":"string"},"payload":{"type":"object"}}}}}}