{"openapi":"3.1.0","info":{"title":"FairCar Public REST API","version":"1.0.0","description":"REST API powering the FairCar native iOS/Android apps and approved third-party clients.\n\n**Base URL:** `https://faircar.app/api/public/v1`\n\n**Authentication.** Public discovery endpoints need no credentials. Everything user-specific\nrequires a Supabase access token in `Authorization: Bearer <jwt>`. Obtain it with the Supabase\nAuth REST API or an official Supabase SDK (`supabase.auth.signInWithPassword`, OAuth, or magic\nlink) against the project below, then refresh it with the SDK before expiry (1 hour).\n\n**Money.** All amounts are USD. Values named `*_cents` are integers in cents; every other\namount is a decimal dollar value. Dates are `YYYY-MM-DD` (trip dates) or ISO-8601 UTC timestamps.\n\n**Errors.** Non-2xx responses always return `{ \"error\": { \"code\", \"message\" } }`.\nCodes: `unauthorized` (401), `forbidden` (403), `not_found` (404), `conflict` (409),\n`validation_failed` (422), `bad_request` (400).\n\n**Booking flow.** `POST /vehicles/{id}/quote` → `POST /bookings` (returns a Stripe Checkout\nclient secret and places a 10-minute hold) → complete payment in a Stripe web view or the\nStripe mobile SDK → poll `GET /bookings/{id}` until `status` is `confirmed`. Abandoned holds\ncan be released immediately with `POST /bookings/{id}/release-hold`.","contact":{"name":"FairCar","url":"https://faircar.app/developers"}},"servers":[{"url":"https://faircar.app/api/public/v1","description":"FairCar production"}],"externalDocs":{"description":"Developer guide","url":"https://faircar.app/developers"},"components":{"securitySchemes":{"SupabaseBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Supabase access token for the signed-in FairCar user. Auth base URL: https://npsimakbiyqxrmwgzhjn.supabase.co/auth/v1"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}},"required":["code","message"]}}},"Pagination":{"type":"object","properties":{"limit":{"type":"number"},"offset":{"type":"number"},"total":{"type":"number"},"has_more":{"type":"boolean"},"next_offset":{"type":["number","null"]}}},"Vehicle":{"type":"object","description":"Public marketplace listing.","properties":{"id":{"type":"string","format":"uuid"},"host_id":{"type":"string","format":"uuid"},"make":{"type":"string"},"model":{"type":"string"},"year":{"type":"number"},"trim":{"type":"string"},"type":{"type":"string"},"body_style":{"type":"string"},"fuel_type":{"type":"string"},"seats":{"type":"number"},"transmission":{"type":"string"},"daily_rate":{"type":"number"},"from_daily_rate":{"type":"number"},"monthly_rate":{"type":"number"},"range_daily_rate":{"type":"number"},"tax_rate":{"type":"number"},"deposit":{"type":"number"},"location":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"headline":{"type":"string"},"description":{"type":"string"},"photos":{"type":"array","items":{"type":"string"}},"features":{"type":"array","items":{"type":"string"}},"instant_book":{"type":"boolean"},"rating":{"type":"number"},"review_count":{"type":"number"},"trips":{"type":"number"},"min_trip_days":{"type":"number"},"max_trip_days":{"type":"number"},"advance_notice_hours":{"type":"number"},"cancellation_policy":{"type":"string","enum":["flexible","moderate","strict"]},"included_miles_per_day":{"type":"number"},"extra_mile_cents":{"type":"number"},"cleaning_fee_cents":{"type":"number"},"delivery_fee_cents":{"type":"number"},"pet_fee_cents":{"type":"number"},"pickup_locations":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"Quote":{"type":"object","properties":{"days":{"type":"number"},"daily_rate":{"type":"number"},"subtotal":{"type":"number"},"discount":{"type":"number"},"guest_fee":{"type":"number"},"tax_rate":{"type":"number"},"tax":{"type":"number"},"total":{"type":"number"},"per_day_all_in":{"type":"number"}}},"Booking":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"vehicle_id":{"type":"string","format":"uuid"},"guest_id":{"type":"string","format":"uuid"},"start_date":{"type":"string","format":"date","example":"2026-10-04"},"end_date":{"type":"string","format":"date","example":"2026-10-04"},"status":{"type":"string","enum":["pending","confirmed","active","completed","reviewed","cancelled","disputed"]},"subtotal":{"type":"number"},"guest_fee":{"type":"number"},"total":{"type":"number"},"protection_tier":{"type":"string"},"created_at":{"type":"string"},"vehicles":{"$ref":"#/components/schemas/Vehicle"}}},"Thread":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"booking_id":{"type":"string","format":"uuid"},"guest_id":{"type":"string","format":"uuid"},"host_id":{"type":"string","format":"uuid"},"last_message_at":{"type":"string"},"last_message_preview":{"type":"string"},"guest_unread_count":{"type":"number"},"host_unread_count":{"type":"number"}}},"Message":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"thread_id":{"type":"string","format":"uuid"},"sender_id":{"type":"string","format":"uuid"},"body":{"type":"string"},"read_at":{"type":"string"},"created_at":{"type":"string"}}},"PublicProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"avatar_url":{"type":"string"},"bio":{"type":"string"},"city":{"type":"string"},"region":{"type":"string"},"country":{"type":"string"},"languages":{"type":"array","items":{"type":"string"}},"level":{"type":"string"},"response_rate":{"type":"number"},"response_time_minutes":{"type":"number"},"member_since":{"type":"string"},"phone_verified":{"type":"boolean"},"identity_verified":{"type":"boolean"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"tags":[{"name":"Meta","description":"Service discovery and health."},{"name":"Discovery","description":"Public search, listings, availability, reviews, pricing."},{"name":"Account","description":"The signed-in user, favourites, trips."},{"name":"Bookings","description":"Create, read, and cancel trips."},{"name":"Messaging","description":"Guest ↔ host conversations."},{"name":"Reviews","description":"Post-trip reviews."},{"name":"Host","description":"Host-side listings, bookings, calendar."}],"paths":{"/":{"get":{"tags":["Meta"],"summary":"Service index","operationId":"getServiceIndex","responses":{"200":{"description":"Endpoint map.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/health":{"get":{"tags":["Meta"],"summary":"Health check","operationId":"getHealth","responses":{"200":{"description":"Service is up.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/openapi.json":{"get":{"tags":["Meta"],"summary":"This specification","operationId":"getOpenApi","responses":{"200":{"description":"OpenAPI document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/vehicles":{"get":{"tags":["Discovery"],"summary":"Search available listings","operationId":"searchVehicles","description":"Returns active listings. When both `start_date` and `end_date` are supplied, unavailable cars are filtered out and per-day pricing reflects those dates.","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"type":"string","format":"date","example":"2026-10-04"},"description":"Trip start (inclusive)."},{"name":"end_date","in":"query","required":false,"schema":{"type":"string","format":"date","example":"2026-10-04"},"description":"Trip end (exclusive)."},{"name":"location","in":"query","required":false,"schema":{"type":"string"},"description":"Case-insensitive match on the listing's city/location text."},{"name":"make","in":"query","required":false,"schema":{"type":"string"},"description":"Vehicle make, e.g. Tesla."},{"name":"model","in":"query","required":false,"schema":{"type":"string"},"description":"Vehicle model."},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Vehicle type/category, e.g. EV, SUV."},{"name":"min_seats","in":"query","required":false,"schema":{"type":"number"},"description":"Minimum seat count."},{"name":"min_daily_rate","in":"query","required":false,"schema":{"type":"number"},"description":"Minimum nightly rate in dollars."},{"name":"max_daily_rate","in":"query","required":false,"schema":{"type":"number"},"description":"Maximum nightly rate in dollars."},{"name":"instant_book","in":"query","required":false,"schema":{"type":"boolean"},"description":"`true` to only return instant-book listings."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["recommended","price_asc","price_desc","rating_desc"]},"description":"Sort order."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"maximum":100},"description":"Page size (1–100)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0},"description":"Page offset."}],"responses":{"200":{"description":"Matching listings.","content":{"application/json":{"schema":{"type":"object","properties":{"vehicles":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}}},"/vehicles/{vehicle_id}":{"get":{"tags":["Discovery"],"summary":"Get one listing","operationId":"getVehicle","parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id."}],"responses":{"200":{"description":"Listing detail.","content":{"application/json":{"schema":{"type":"object","properties":{"vehicle":{"$ref":"#/components/schemas/Vehicle"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/vehicles/{vehicle_id}/availability":{"get":{"tags":["Discovery"],"summary":"Unavailable dates","operationId":"getVehicleAvailability","description":"Dates from today forward that are blocked by the host or already booked.","parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id."}],"responses":{"200":{"description":"Blocked and booked dates.","content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_id":{"type":"string","format":"uuid"},"unavailable_dates":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date","example":"2026-10-04"},"status":{"type":"string","enum":["blocked","booked"]}}}}}}}}}}}},"/vehicles/{vehicle_id}/reviews":{"get":{"tags":["Discovery"],"summary":"Listing reviews and rating","operationId":"listVehicleReviews","parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id."}],"responses":{"200":{"description":"Reviews plus aggregate rating.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/vehicles/{vehicle_id}/quote":{"post":{"tags":["Discovery"],"summary":"Price a trip","operationId":"quoteVehicle","description":"Authoritative base quote (the same numbers the website shows on search, listing and checkout). Extras are added at booking time and can only increase the total.","parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start_date":{"type":"string","format":"date","example":"2026-10-04"},"end_date":{"type":"string","format":"date","example":"2026-10-04"}},"required":["start_date","end_date"]}}}},"responses":{"200":{"description":"Quote breakdown.","content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_id":{"type":"string","format":"uuid"},"start_date":{"type":"string","format":"date","example":"2026-10-04"},"end_date":{"type":"string","format":"date","example":"2026-10-04"},"currency":{"type":"string"},"quote":{"$ref":"#/components/schemas/Quote"}}}}}}}}},"/cities":{"get":{"tags":["Discovery"],"summary":"Supported cities","operationId":"listCities","responses":{"200":{"description":"City list with slugs used by the website.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/airports":{"get":{"tags":["Discovery"],"summary":"Supported airports","operationId":"listAirports","responses":{"200":{"description":"Airport list with IATA codes.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/profiles/{user_id}":{"get":{"tags":["Discovery"],"summary":"Public profile","operationId":"getPublicProfile","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"FairCar user id."}],"responses":{"200":{"description":"Public profile and badges.","content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"$ref":"#/components/schemas/PublicProfile"},"badges":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/auth/token":{"post":{"tags":["Account"],"summary":"Exchange an API key for an access token","description":"Send a personal API key (created in Account → API keys) either in the body as `api_key` or in the `X-API-Key` header. Returns a short-lived Supabase access token to use as `Authorization: Bearer <access_token>`.","operationId":"exchangeApiKey","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"type":"string"}}}}}},"responses":{"200":{"description":"Access token minted.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"tags":["Account"],"summary":"Signed-in user","operationId":"getMe","security":[{"SupabaseBearer":[]}],"responses":{"200":{"description":"Account, roles and profile.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"patch":{"tags":["Account"],"summary":"Update my profile","operationId":"updateMe","security":[{"SupabaseBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":"string"},"bio":{"type":"string"},"city":{"type":"string"},"region":{"type":"string"},"country":{"type":"string"},"languages":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated account.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me/favorites":{"get":{"tags":["Account"],"summary":"My saved cars","operationId":"listFavorites","security":[{"SupabaseBearer":[]}],"responses":{"200":{"description":"Saved listings.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Account"],"summary":"Toggle a saved car","operationId":"toggleFavorite","security":[{"SupabaseBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_id":{"type":"string","format":"uuid"}},"required":["vehicle_id"]}}}},"responses":{"200":{"description":"New saved state.","content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_id":{"type":"string","format":"uuid"},"favorited":{"type":"boolean"}}}}}}}}},"/me/trips":{"get":{"tags":["Account"],"summary":"My trips as a guest","operationId":"listMyTrips","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by booking status."}],"responses":{"200":{"description":"Trips, newest start date first.","content":{"application/json":{"schema":{"type":"object","properties":{"trips":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}}}}}}}}}},"/bookings":{"get":{"tags":["Bookings"],"summary":"List my bookings","operationId":"listBookings","security":[{"SupabaseBearer":[]}],"responses":{"200":{"description":"Bookings for the signed-in guest.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"post":{"tags":["Bookings"],"summary":"Create a booking and start payment","operationId":"createBooking","security":[{"SupabaseBearer":[]}],"description":"Validates host rules (minimum/maximum trip length, advance notice, age and verification gates), places a 10-minute availability hold, and returns a Stripe Checkout client secret. The booking becomes `confirmed` only after Stripe reports payment success.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_id":{"type":"string","format":"uuid"},"start_date":{"type":"string","format":"date","example":"2026-10-04"},"end_date":{"type":"string","format":"date","example":"2026-10-04"},"return_url":{"type":"string","format":"uri","description":"Deep link or URL Stripe returns to after payment."},"environment":{"type":"string","enum":["sandbox","live"]},"protection_tier":{"type":"string","enum":["basic","standard","premium"],"default":"basic"},"delivery":{"type":"boolean"},"pets":{"type":"boolean"},"additional_driver":{"type":"boolean"},"child_seat":{"type":"boolean"},"prepaid_ev_charge":{"type":"boolean"},"unlimited_miles":{"type":"boolean"},"promo_code":{"type":"string"},"selected_addons":{"type":"array","items":{"type":"object","properties":{"vehicle_addon_id":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1,"maximum":20},"tier_index":{"type":["integer","null"]}},"required":["vehicle_addon_id","quantity"]}}},"required":["vehicle_id","start_date","end_date","return_url","environment"]}}}},"responses":{"201":{"description":"Booking held; finish payment with the client secret.","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"payment":{"type":"object","properties":{"provider":{"type":"string"},"mode":{"type":"string"},"client_secret":{"type":"string"},"instructions":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Dates unavailable or host rules not met.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{booking_id}":{"get":{"tags":["Bookings"],"summary":"Booking detail","operationId":"getBooking","security":[{"SupabaseBearer":[]}],"description":"Readable by the guest and the host. Pickup, return and Wi-Fi details are included only once the caller is authorised for them.","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Booking id."}],"responses":{"200":{"description":"Booking, host profile and any review.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"403":{"description":"Not a party to this booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/bookings/{booking_id}/cancellation-quote":{"get":{"tags":["Bookings"],"summary":"Preview a cancellation refund","operationId":"getCancellationQuote","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Booking id."}],"responses":{"200":{"description":"Refund percentage, amount and policy label.","content":{"application/json":{"schema":{"type":"object","properties":{"booking_id":{"type":"string","format":"uuid"},"refund_pct":{"type":"number"},"refund_amount":{"type":"number"},"label":{"type":"string"}}}}}}}}},"/bookings/{booking_id}/cancel":{"post":{"tags":["Bookings"],"summary":"Cancel a booking","operationId":"cancelBooking","security":[{"SupabaseBearer":[]}],"description":"Guest or host may cancel. Refunds follow the listing's cancellation policy; host cancellations always refund in full.","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Booking id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Cancellation result including refund status.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"409":{"description":"Trip already cancelled or completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/bookings/{booking_id}/release-hold":{"post":{"tags":["Bookings"],"summary":"Release an unpaid hold","operationId":"releaseBookingHold","security":[{"SupabaseBearer":[]}],"description":"Call when the guest abandons checkout so the dates free up immediately instead of waiting for the 10-minute expiry.","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Booking id."}],"responses":{"200":{"description":"Hold released.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/threads":{"get":{"tags":["Messaging"],"summary":"My conversations","operationId":"listThreads","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"role","in":"query","required":false,"schema":{"type":"string","enum":["guest","host"],"default":"guest"},"description":"Which side of the conversation to list."}],"responses":{"200":{"description":"Threads plus unread counts.","content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string"},"threads":{"type":"array","items":{"$ref":"#/components/schemas/Thread"}},"unread":{"type":"object","additionalProperties":true}}}}}}}}},"/threads/{thread_id}":{"get":{"tags":["Messaging"],"summary":"Thread with messages","operationId":"getThread","security":[{"SupabaseBearer":[]}],"description":"Marks the thread read for the calling user.","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Thread id."}],"responses":{"200":{"description":"Thread and messages.","content":{"application/json":{"schema":{"type":"object","properties":{"thread":{"$ref":"#/components/schemas/Thread"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}}}}},"/threads/{thread_id}/messages":{"get":{"tags":["Messaging"],"summary":"List messages","operationId":"listMessages","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Thread id."}],"responses":{"200":{"description":"Messages oldest first.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"post":{"tags":["Messaging"],"summary":"Send a message","operationId":"sendMessage","security":[{"SupabaseBearer":[]}],"description":"Rate limited per sender per thread; blocked more than 14 days after a trip ends unless the trip is disputed.","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Thread id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000}},"required":["body"]}}}},"responses":{"201":{"description":"Message created.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}}}}},"/reviews":{"get":{"tags":["Reviews"],"summary":"Reviews I still owe","operationId":"listPendingReviews","security":[{"SupabaseBearer":[]}],"responses":{"200":{"description":"Completed trips awaiting a review.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"post":{"tags":["Reviews"],"summary":"Submit a review","operationId":"createReview","security":[{"SupabaseBearer":[]}],"description":"Allowed once the trip is `completed`. Direction (guest→host or host→guest) is inferred from the caller.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"booking_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":"string","maxLength":2000},"tags":{"type":"array","items":{"type":"string"},"maxItems":12}},"required":["booking_id","rating"]}}}},"responses":{"201":{"description":"Review submitted."}}}},"/host/listings":{"get":{"tags":["Host"],"summary":"My listings","operationId":"listHostListings","security":[{"SupabaseBearer":[]}],"description":"All listings owned by the calling host, including paused and unpublished ones.","responses":{"200":{"description":"Host listings.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/host/bookings":{"get":{"tags":["Host"],"summary":"Bookings on my cars","operationId":"listHostBookings","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by booking status."}],"responses":{"200":{"description":"Host-side bookings.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/host/vehicles/{vehicle_id}/calendar":{"get":{"tags":["Host"],"summary":"Listing calendar","operationId":"getHostCalendar","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id owned by the caller."}],"responses":{"200":{"description":"Blocked days and booking spans with guest metadata.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}},"post":{"tags":["Host"],"summary":"Block or unblock dates","operationId":"setHostCalendarBlocks","security":[{"SupabaseBearer":[]}],"parameters":[{"name":"vehicle_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Listing id owned by the caller."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"blocked_dates":{"type":"array","items":{"type":"string","format":"date","example":"2026-10-04"},"maxItems":366},"block":{"type":"boolean","default":true,"description":"`true` blocks the dates, `false` unblocks them."}},"required":["blocked_dates"]}}}},"responses":{"200":{"description":"Calendar updated.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}}}}