{"openapi":"3.1.0","info":{"title":"Yondry API","version":"0.1.0"},"paths":{"/api/v1/app-version":{"get":{"tags":["app-meta"],"summary":"App Version","operationId":"app_version_api_v1_app_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppVersionResponse"}}}}}}},"/api/v1/admin/tenants":{"post":{"tags":["admin"],"summary":"Create Tenant","description":"Provision a new B2B tenant and generate its API key.\nThe plaintext key is returned once — it cannot be recovered later.\nRequires X-Admin-Key header matching ADMIN_SECRET_KEY.","operationId":"create_tenant_api_v1_admin_tenants_post","parameters":[{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/tenants/{slug}/usage":{"get":{"tags":["admin"],"summary":"Get Tenant Usage","description":"Return usage stats for a tenant: plans this month and all time.","operationId":"get_tenant_usage_api_v1_admin_tenants__slug__usage_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantUsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/demo-stats":{"get":{"tags":["admin"],"summary":"Demo Stats","description":"Per-partner opens and plans for the public demo page.\n\n`unique_visitors` counts distinct session ids, so one person reloading the\npage — or you running the pre-demo checklist — collapses to 1 rather than\ninflating the open count. Partners are ordered by views, so the link that\nactually got traction sorts to the top.","operationId":"demo_stats_api_v1_admin_demo_stats_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days","default":90,"title":"Days"},"description":"Window size in days"},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/social-saves/extraction-stats":{"get":{"tags":["admin"],"summary":"Social Save Extraction Stats","description":"Counts of social saves grouped by extraction_layer over the last 30 days.","operationId":"social_save_extraction_stats_api_v1_admin_social_saves_extraction_stats_get","parameters":[{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialSaveExtractionStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/tenants/{slug}/apify-usage":{"get":{"tags":["admin"],"summary":"Get Apify Usage","description":"Apify scraping usage stats for a tenant over the last 30 days.","operationId":"get_apify_usage_api_v1_admin_tenants__slug__apify_usage_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApifyUsageStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/tenants/{slug}/limit":{"patch":{"tags":["admin"],"summary":"Update Tenant Limit","description":"Update the monthly plan limit for a tenant. Set to null for unlimited.","operationId":"update_tenant_limit_api_v1_admin_tenants__slug__limit_patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTenantLimitRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantUsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/tenants/{slug}/branding":{"patch":{"tags":["admin"],"summary":"Update Tenant Branding","description":"Set white-label branding for a tenant's embeddable widget.\n\nPer field: null = leave unchanged, \"\" = clear it, value = set it. Unset\nfields are preserved. Fetched by the widget via GET /b2b/branding.","operationId":"update_tenant_branding_api_v1_admin_tenants__slug__branding_patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTenantBrandingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBrandingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/tenants/{slug}/apify":{"patch":{"tags":["admin"],"summary":"Update Tenant Apify","description":"Toggle the Apify Instagram-scraper extractor for a tenant and/or set its\nper-day run cap. Per field: null (omitted) = leave unchanged.\n\nThis is the runtime replacement for what migrations 0035/0036\n(`enable_apify_yondry_tenant` / `disable_apify_yondry_tenant`) did as\none-off data mutations — they exist only to flip `tenants.apify_enabled`\n/`apify_daily_limit` for one hardcoded tenant row, which is config, not\nschema; see docs/plan/2026-08-25-remediation-roadmap.md task 5.8 /\nfinding M9. Those two migrations are left as-is (already run in\nproduction, never edit a landed migration) — this endpoint is the\nintended path going forward. **Do not write another migration to flip a\ntenant's Apify flags; use this endpoint instead.**","operationId":"update_tenant_apify_api_v1_admin_tenants__slug__apify_patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTenantApifyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantApifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/guides/generate":{"post":{"tags":["admin"],"summary":"Generate Neighbourhood Guide","description":"Generate (or regenerate) a neighbourhood guide and persist it.\n\nOffline content step only — never called from a live planner/MCP request path.\nGoogle Places resolves each of `place_queries` for real coordinates/hours/rating\nfirst; Claude is only given that verified list and writes copy about it, never\nfree-standing facts. Any query that doesn't resolve, or that Claude doesn't\nwrite a \"why\" for, is dropped rather than guessed at.","operationId":"generate_neighbourhood_guide_api_v1_admin_guides_generate_post","parameters":[{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateGuideRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateGuideResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/guides/{slug}/scenarios/generate":{"post":{"tags":["admin"],"summary":"Generate Guide Scenarios","description":"Generate (or regenerate) the three scenario plans for an existing guide:\nclassic_saturday, rainy_day, evening_out. Each is a real plan produced by\ncalling the live NL planner pipeline (Haiku + Sonnet + weather + Google\nPlaces) anchored at the guide's lat/lon — not hand-written content.\n\nOffline content step only, like /guides/generate. Overwrites any\npreviously generated scenario_plans for this guide.","operationId":"generate_guide_scenarios_api_v1_admin_guides__slug__scenarios_generate_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateScenariosResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/metrics":{"get":{"tags":["admin"],"summary":"Launch Metrics","description":"Activation, usage, and monetisation-signal metrics for the consumer launch.\n\nRequires X-Admin-Key. `totals` are state as-of-now (all-time); `window_summary`\nand `daily` cover the last `days` days (e.g. days=1 for today, days=7 for the\nlast week). Consumer plans = GeneratedPlan rows with a non-null user_id\n(anonymous MCP plan_day rows have user_id=None and are excluded). Per-day\nbuckets are computed in Python so the query is identical on Postgres + sqlite.","operationId":"launch_metrics_api_v1_admin_metrics_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days for the time series + summary","default":30,"title":"Days"},"description":"Window size in days for the time series + summary"},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchMetricsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/admin/users/comp":{"post":{"tags":["admin"],"summary":"Comp User","description":"Grant/revoke free Plus for a user by email. Requires X-Admin-Key.\n\nFixes the \"we keep gating ourselves / how do I comp someone\" problem\nwithout hand-editing production rows.","operationId":"comp_user_api_v1_admin_users_comp_post","parameters":[{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompUserRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompUserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/billing/revenuecat":{"post":{"tags":["billing"],"summary":"Revenuecat Webhook","operationId":"revenuecat_webhook_api_v1_billing_revenuecat_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RCWebhookPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revenuecat Webhook Api V1 Billing Revenuecat Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/b2b/planner/query":{"post":{"tags":["b2b"],"summary":"B2B Nl Query","description":"Natural-language planner for B2B callers.\n\nNo end-user account required — the caller supplies a lat/lon instead of a\nuser JWT. The planner uses Google Places gap-fill to source nearby\nattractions (the same mechanism used when a registered user's library is\nsparse in the requested area).\n\nReturns 429 when the tenant's monthly plan cap is exceeded.","operationId":"b2b_nl_query_api_v1_b2b_planner_query_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/B2BPlannerRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/B2BPlannerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/b2b/usage":{"get":{"tags":["b2b"],"summary":"B2B Usage","description":"Return this tenant's plan usage for the current calendar month (UTC).","operationId":"b2b_usage_api_v1_b2b_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/B2BUsageResponse"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/b2b/branding":{"get":{"tags":["b2b"],"summary":"B2B Branding","description":"White-label branding for this tenant's embeddable widget.","operationId":"b2b_branding_api_v1_b2b_branding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/B2BBrandingResponse"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/b2b/insights":{"get":{"tags":["b2b"],"summary":"B2B Insights","description":"Usage analytics for this tenant: top moods, place types, and companions.","operationId":"b2b_insights_api_v1_b2b_insights_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/B2BInsightsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/demo/plan":{"post":{"tags":["demo"],"summary":"Demo Plan","description":"Keyless planner for the public demo page. Internally authenticates as the\ndemo B2B tenant — no X-API-Key header required from the caller.\n\nAbuse protection: Origin allowlist + per-IP rate limit + tenant monthly cap.","operationId":"demo_plan_api_v1_demo_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoPlanRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoPlanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/demo/track":{"post":{"tags":["demo"],"summary":"Demo Track","description":"Record that a partner's demo link was opened.\n\nReturns 204 whatever happens. This is fired from the page on load, so a\nfailure here must be invisible to the visitor — a prospect must never see\nthe demo break because attribution had a bad day.","operationId":"demo_track_api_v1_demo_track_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoTrackRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/guides":{"get":{"tags":["guides"],"summary":"List Guides Route","operationId":"list_guides_route_api_v1_guides_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GuideSummary"},"type":"array","title":"Response List Guides Route Api V1 Guides Get"}}}}}}},"/api/v1/guides/{slug}":{"get":{"tags":["guides"],"summary":"Get Guide Route","operationId":"get_guide_route_api_v1_guides__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuideResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/campaigns/active":{"get":{"tags":["campaigns"],"summary":"Get Active Campaign","description":"The campaign live right now, or 204 when there is none.\n\n204 rather than an empty object so the app can branch on the status code\nand render nothing without parsing a body. Off-season and\nkill-switch-disabled are deliberately indistinguishable from out here.","operationId":"get_active_campaign_api_v1_campaigns_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/CampaignResponse"},{"type":"null"}],"title":"Response Get Active Campaign Api V1 Campaigns Active Get"}}}},"204":{"description":"No campaign is currently active"}}}},"/api/v1/campaigns/{slug}/venues":{"get":{"tags":["campaigns"],"summary":"Get Campaign Venues","description":"Up to 8 genuine anchor venues near the caller, pinned first then nearest.\n\n404 when the slug is not the live campaign — including when it is a real\ncampaign that is out of season, since a stale app build asking for last\nyear's event should get the same answer as one asking for nonsense.","operationId":"get_campaign_venues_api_v1_campaigns__slug__venues_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"lat","in":"query","required":true,"schema":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"}},{"name":"lng","in":"query","required":true,"schema":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lng"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignVenuesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/register":{"post":{"tags":["auth"],"summary":"Register","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["auth"],"summary":"Login","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh","operationId":"refresh_api_v1_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/social":{"post":{"tags":["auth"],"summary":"Social Auth","description":"Sign in with Apple or Google.\n\nThe mobile client obtains an id_token from the provider SDK and forwards\nit here. The backend verifies the token, finds or creates the user, and\nreturns a token pair identical to the email/password login response plus\na ``user`` block and ``is_new_user`` flag so the client can route to\nonboarding when appropriate.","operationId":"social_auth_api_v1_auth_social_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialAuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/upgrade":{"post":{"tags":["auth"],"summary":"Upgrade Guest Account","description":"Attach a verified Apple or Google identity to the caller's guest account.\n\nThis exists because ``POST /auth/social`` cannot do it. That endpoint takes\nno current user: it calls ``find_or_create_user()``, which matches on the\nprovider id, then on a verified email, and otherwise creates a **new row**.\nA guest who signs in there keeps nothing — their saved places and plans stay\non the account they just walked away from, orphaned at the exact moment they\ndecided to commit.\n\nHere the row is mutated in place, so the id never changes and every place,\nplan, collection and visit stays attached. The caller's existing tokens\nremain valid for the same reason.\n\nA provider identity already in use is **refused, never merged**. Merging two\npopulated accounts means choosing which home location, which collections and\nwhich history survive, and getting that wrong destroys data silently. The\nfriction of \"sign in instead\" is recoverable; a bad merge is not.","operationId":"upgrade_guest_account_api_v1_auth_upgrade_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestUpgradeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/guest":{"post":{"tags":["auth"],"summary":"Guest Login","description":"Create a fresh isolated guest account per call.\nEach device session gets its own user row so places, plans, and home\nlocation are never shared between guests.\nThe stored refresh token means a returning guest re-enters their own\naccount (not a new one) as long as their token is valid.","operationId":"guest_login_api_v1_auth_guest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}}}}},"/api/v1/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","operationId":"logout_api_v1_auth_logout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/users/me":{"get":{"tags":["users"],"summary":"Get Me","description":"Return the authenticated user's full profile.","operationId":"get_me_api_v1_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["privacy"],"summary":"Full account erasure (UK GDPR Art. 17)","description":"Permanently erase all personal data and anonymise the user record.\n\nImmediate effects:\n- All Place, Visit, and GeneratedPlan data deleted (preserve_edited=False).\n- All refresh tokens revoked — existing sessions stop immediately.\n- Sensitive fields nulled: home_lat/lon, location, name, avatar.\n- Email scrambled to 'deleted-{uuid}@deleted.invalid'.\n- is_deleted=True, is_active=False.\n\nThe User row itself is retained for audit purposes.\nAccess tokens (stateless JWTs) become invalid because get_current_user\nrejects is_deleted=True accounts.","operationId":"delete_account_api_v1_users_me_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]},"patch":{"tags":["users"],"summary":"Update Me","description":"Partial update of home/radius settings.\n\nOnly supplied fields are changed; omitted fields remain unchanged.\nAccepts both new field names (home_lng, home_address, default_radius_km,\nradius_unit) and legacy names (home_lon, home_label, travel_radius_km)\nfor backward compatibility.\n\nValidates:\n  - radius 2–300 km (or the legacy > 0–300 range for travel_radius_km)\n  - radius_unit must be 'km' or 'miles'","operationId":"update_me_api_v1_users_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateSettings"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/complete-onboarding":{"post":{"tags":["users"],"summary":"Complete Onboarding","description":"Atomically record all onboarding data and mark onboarding as complete.\n\nIdempotent: safe to call again if onboarding is already done (e.g. user\ngoes back and re-submits the onboarding flow). Returns 200 in both cases.","operationId":"complete_onboarding_api_v1_users_me_complete_onboarding_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateOnboarding"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/location":{"patch":{"tags":["users"],"summary":"Silent background location update (fire-and-forget)","description":"Record the user's current device location for planning purposes.\n\nReturns 204 No Content — the mobile client does not need to await a body.\nThe last_location fields are used by resolve_planning_location as a\nsecondary planning origin when the user is far from home.\n\nRequires DEVICE_LOCATION consent (EXPLICIT tier). Returns 403 if not\ngranted — the mobile client should guide the user to the privacy settings.","operationId":"update_location_api_v1_users_me_location_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateLocation"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/transport":{"patch":{"tags":["users"],"summary":"Update Transport","description":"Update the user's transport mode preferences.\n\nValidates that all supplied modes are in {driving, walking, cycling, transit}\nand that the list is non-empty.  Sets primary_transport_mode to modes[0].\n\nIf update_radius=True, also resets default_radius_km to the default radius\nfor the primary mode (e.g. walking → 5 km, driving → 50 km).","operationId":"update_transport_api_v1_users_me_transport_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransportModeUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/push-token":{"put":{"tags":["users"],"summary":"Register or refresh the Expo push token for this device","description":"Store or replace the Expo push token for the authenticated user's device.\n\nCalled on app launch whenever push permission is granted. Returns 204.","operationId":"update_push_token_api_v1_users_me_push_token_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushTokenUpdate"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/sync-status":{"get":{"tags":["users"],"summary":"Get Sync Status","description":"Lightweight staleness check for mobile foreground refresh.\n\nReturns the MAX(updated_at) for each entity type the client caches.\nMobile compares these against its local \"last fetched\" timestamps and\nonly re-fetches the endpoints that have changed since the last sync.\n\nAll queries are single-aggregate SELECTs — no joins, no heavy scanning.","operationId":"get_sync_status_api_v1_users_me_sync_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncStatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/export":{"get":{"tags":["users"],"summary":"Export all personal data (UK GDPR Art. 15/20)","description":"Return a complete, machine-readable export of all personal data held for\nthe authenticated user (UK GDPR Art. 15 / Art. 20).","operationId":"export_my_data_api_v1_users_me_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDataExport"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/users/me/children":{"put":{"tags":["users"],"summary":"Replace Children","description":"Replace the user's full child list (idempotent re-submit on each onboarding save).","operationId":"replace_children_api_v1_users_me_children_put","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ChildIn"},"type":"array","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/places":{"get":{"tags":["places"],"summary":"List Places","operationId":"list_places_api_v1_places_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"}},{"name":"place_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}],"title":"Place Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/PlaceStatus"},{"type":"null"}],"title":"Status"}},{"name":"is_favourite","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Favourite"}},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search"}},{"name":"near_lat","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Near Lat"}},{"name":"near_lon","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Near Lon"}},{"name":"radius_km","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Radius Km"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponse_PlaceListItem_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["places"],"summary":"Create Place","operationId":"create_place_api_v1_places_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}":{"get":{"tags":["places"],"summary":"Get Place","operationId":"get_place_api_v1_places__place_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["places"],"summary":"Update Place","operationId":"update_place_api_v1_places__place_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["places"],"summary":"Delete Place","operationId":"delete_place_api_v1_places__place_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/restore":{"post":{"tags":["places"],"summary":"Restore Place","operationId":"restore_place_api_v1_places__place_id__restore_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/enrich":{"post":{"tags":["places"],"summary":"Enrich Place","operationId":"enrich_place_api_v1_places__place_id__enrich_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/feedback":{"post":{"tags":["places"],"summary":"Set Feedback","operationId":"set_feedback_api_v1_places__place_id__feedback_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeedbackRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/extraction-trace":{"get":{"tags":["places"],"summary":"Get Extraction Trace","description":"Dev endpoint: return the full extraction trace for a socially-saved place.\n\nReturns partial data (place coords only) when the place was not imported via\nthe social-save pipeline. Used by the __DEV__ debug panel in the mobile app.","operationId":"get_extraction_trace_api_v1_places__place_id__extraction_trace_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractionTraceOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/import-from-url":{"post":{"tags":["places"],"summary":"Import Place From Url","operationId":"import_place_from_url_api_v1_places_import_from_url_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportFromUrlRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/places/unfurl":{"post":{"tags":["places"],"summary":"Unfurl Place From Url","description":"Extract place data from any URL.\n\nRuns the full extraction pipeline (JSON-LD → OG tags → Claude AI →\nGoogle Places) and returns structured place data.\n\nWhen the pipeline fully resolves a place, the Place + PlaceSource are\nsaved immediately and `saved_place_id` is set in the response. For\nunresolved results only the preview is returned — the client shows a\nconfirmation card and then POSTs to /places to save.","operationId":"unfurl_place_from_url_api_v1_places_unfurl_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnfurlRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnfurlResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/places/import-from-image":{"post":{"tags":["places"],"summary":"Import Place From Image","description":"Extract place data from an uploaded image using Claude Sonnet vision.\n\nAccepts multipart/form-data with a single \"image\" field. The file is\nread entirely into memory (max 10 MB). Claude vision extracts place\nnames, addresses, and coordinates; high/medium confidence places are\nthen verified against Google Places.\n\nIf at least one place is resolved, a Place + PlaceSource record are\nsaved immediately and saved_place_id is set in the response.\nUnresolved results are returned as-is for the client to confirm.\n\nRaises:\n    413 if the Content-Length header or actual body exceeds 10 MB.\n    422 if the media type is not image/jpeg, image/png, image/gif,\n        or image/webp.","operationId":"import_place_from_image_api_v1_places_import_from_image_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_import_place_from_image_api_v1_places_import_from_image_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnfurlResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/places/import-google-takeout":{"post":{"tags":["places"],"summary":"Import Google Takeout","description":"Bulk-import saved places from a Google Takeout 'Saved Places.json' file.\n\nAccepts multipart/form-data with a single 'file' field containing the\nGeoJSON FeatureCollection exported by Google Takeout.  Max file size: 20 MB.\n\nDeduplicates against the user's existing library by name (case-insensitive).\nReturns a summary of imported / skipped / failed counts.","operationId":"import_google_takeout_api_v1_places_import_google_takeout_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_import_google_takeout_api_v1_places_import_google_takeout_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleTakeoutImportResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/visits":{"post":{"tags":["visits"],"summary":"Create Visit","description":"Create a rich visit with optional rating, notes, and duration.","operationId":"create_visit_api_v1_visits_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["visits"],"summary":"List Visits","description":"List visits for the authenticated user, newest first.","operationId":"list_visits_api_v1_visits_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Place Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VisitResponse"},"title":"Response List Visits Api V1 Visits Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/visits/simple":{"post":{"tags":["visits"],"summary":"Create Simple Visit","description":"Backward-compatible simple check-in used by PlaceDetailScreen.","operationId":"create_simple_visit_api_v1_visits_simple_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVisitRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/visits/stats":{"get":{"tags":["visits"],"summary":"Visit Stats","description":"Get aggregate visit statistics for a specific place.","operationId":"visit_stats_api_v1_visits_stats_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitStats"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/visits/{visit_id}":{"delete":{"tags":["visits"],"summary":"Delete Visit","description":"Permanently delete a visit record.","operationId":"delete_visit_api_v1_visits__visit_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"visit_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Visit Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/diagnose":{"get":{"tags":["planner","debug"],"summary":"Diagnose","description":"Admin-only diagnostic endpoint — returns Anthropic key status and a live test call.\n\nRequires the X-Admin-Key header to match settings.ADMIN_SECRET_KEY — see\napp.api.deps.require_admin_key for the status codes and their rationale\n(404 if unconfigured, 403 on key mismatch).","operationId":"diagnose_api_v1_planner_diagnose_get","parameters":[{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Diagnose Api V1 Planner Diagnose Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/today":{"get":{"tags":["planner"],"summary":"Get Today Ideas","description":"Return 1–2 proactive plan ideas tailored to the current time of day,\nthe user's home location, and their companion preference.\n\nResults are cached per user for 30 minutes so repeated app opens don't\ntrigger new Claude calls.  Requires the user to have a home location set;\nreturns an empty list otherwise.\n\n`timezone` is the caller's IANA zone name (e.g. \"Europe/London\"); when\nomitted or unrecognised, the time-of-day bucket falls back to UTC.","operationId":"get_today_ideas_api_v1_planner_today_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"timezone","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TodayIdea"},"title":"Response Get Today Ideas Api V1 Planner Today Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/plan-day":{"post":{"tags":["planner"],"summary":"Plan Day","description":"Generate 1–2 day-plan options for the authenticated user.\n\nThe scoring engine reads only the user's own saved places and feedback,\nso the response is always scoped to that user's data.\n\nWeather stub: if `weather` is not supplied the backend defaults to \"sunny\".\nReal weather integration is Task 9.","operationId":"plan_day_api_v1_planner_plan_day_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanDayRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanDayResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/planner/discover-attractions":{"post":{"tags":["planner"],"summary":"Discover Attractions","description":"Return highly-rated, popular places near the given coordinates.\n\nUsed to populate the must-have places selector before plan generation.\nAlso signals (suggest=True) when the user's library is sparse in the area\nso the mobile client can auto-trigger the selector.","operationId":"discover_attractions_api_v1_planner_discover_attractions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverAttractionsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverAttractionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/planner/scout-destinations":{"post":{"tags":["planner"],"summary":"Scout Destination Areas","description":"Propose 2-3 candidate day-trip destination areas for a discovery query.\n\nExtracts structured intent (Claude Haiku), then calls Claude Sonnet to\nscout areas that fit the user's discovery constraints. Each candidate is\ngeocoded server-side so the client can pass zone_lat / zone_lon directly\nto POST /planner/query without an extra round-trip.\n\nTypical flow:\n  1. User types \"something coastal and family-friendly, not too far\"\n  2. Client calls POST /planner/scout-destinations\n  3. User picks a candidate (e.g. Brighton)\n  4. Client calls POST /planner/query with zone_lat / zone_lon / zone_name","operationId":"scout_destination_areas_api_v1_planner_scout_destinations_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/planner/query":{"post":{"tags":["planner"],"summary":"Nl Query","description":"Natural-language planner endpoint.\n\nAccepts a free-text query and returns 1-2 structured itinerary plans.\nThe response is persisted so the user can retrieve their history and\nsubmit feedback on individual plans.\n\nSteps (inside NLPlannerService.plan):\n  1. Claude Haiku extracts structured intent from the query\n  2. User's library places are haversine-filtered; weather fetched concurrently\n  3. Google Places gap-fills types with fewer than 2 library results\n  4. Claude Sonnet generates a single coherent day plan (returned as a\n     one-item list for API backward compatibility)\n\nRaises:\n  422  if Claude Haiku cannot parse the query into structured intent\n  500  if Claude Sonnet cannot produce a valid plan","operationId":"nl_query_api_v1_planner_query_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlannerRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlannerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/planner/{plan_id}/events":{"post":{"tags":["planner"],"summary":"Log Plan Event","description":"Log a client-side plan interaction event (alternative swapped in, stop\nskipped, plan started/completed/abandoned). Server-visible interactions\n(refinements, swap regenerations) are logged automatically by /query —\nthis endpoint exists for the ones only the app can see.","operationId":"log_plan_event_api_v1_planner__plan_id__events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Plan Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanEventRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/query/{plan_id}/feedback":{"post":{"tags":["planner"],"summary":"Nl Plan Feedback","description":"Submit thumbs_up / thumbs_down feedback for a generated plan.\nOverwrites any previous feedback for the same plan.\nTriggers a background taste-profile refresh after saving.","operationId":"nl_plan_feedback_api_v1_planner_query__plan_id__feedback_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Plan Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlannerFeedbackRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/refresh-taste-profile":{"post":{"tags":["planner"],"summary":"Refresh Taste Profile","description":"Manually trigger a taste-profile rebuild for the authenticated user.\nReturns the new profile summary on success.","operationId":"refresh_taste_profile_api_v1_planner_refresh_taste_profile_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Refresh Taste Profile Api V1 Planner Refresh Taste Profile Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/planner/admin/taste-profile/{user_id}":{"get":{"tags":["planner"],"summary":"Admin Taste Profile","description":"Debug endpoint — returns the current taste profile and queue stats for a user.\nRequires the X-Admin-Key header to match settings.ADMIN_SECRET_KEY — see\napp.api.deps.require_admin_key for the status codes and their rationale\n(404 if unconfigured, 403 on key mismatch).","operationId":"admin_taste_profile_api_v1_planner_admin_taste_profile__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"X-Admin-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Admin-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Taste Profile Api V1 Planner Admin Taste Profile  User Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/{plan_id}/share":{"post":{"tags":["planner"],"summary":"Create Share Link","description":"Create (or return existing) a public share link for a plan.\n\nShare is explicit opt-in — calling this endpoint is the user's consent.\nReturns the stable URL for the plan's public page.","operationId":"create_share_link_api_v1_planner__plan_id__share_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Plan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Share Link Api V1 Planner  Plan Id  Share Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/{plan_id}/save":{"post":{"tags":["planner"],"summary":"Save Plan","description":"Mark a generated plan as explicitly saved (bookmarked).","operationId":"save_plan_api_v1_planner__plan_id__save_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Plan Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/history":{"get":{"tags":["planner"],"summary":"Nl Plan History","description":"Return the authenticated user's NL planner history, newest first.\n\nThe default was 20 with no pagination in the app, so a user's twenty-first\nplan simply was not in their history — which reads as \"sometimes I can find\nmy old plans and sometimes I cannot\" rather than as a limit.","operationId":"nl_plan_history_api_v1_planner_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GeneratedPlanSummary"},"title":"Response Nl Plan History Api V1 Planner History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/planner/history/{plan_id}":{"get":{"tags":["planner"],"summary":"Nl Plan Detail","description":"One stored plan, in full, so a past day can be opened again.\n\nThe app had no way to do this. The only action on a history entry was \"Use\nagain\", which re-runs the query and generates a *different* day — so the\nplan itself, with its stops, times and coordinates, was written on every\nquery and never read back. Hundreds of stored days, none of them viewable.\n\nScoped to the caller: another user's plan is a 404, not a 403, so the\nendpoint does not confirm that an id exists.","operationId":"nl_plan_detail_api_v1_planner_history__plan_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"plan_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Plan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratedPlanDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/social-saves/{social_save_id}":{"get":{"tags":["social-saves"],"summary":"Get Social Save","operationId":"get_social_save_api_v1_social_saves__social_save_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"social_save_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Social Save Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialSaveOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/social-saves/import":{"post":{"tags":["social-saves"],"summary":"Import Social Url","operationId":"import_social_url_api_v1_social_saves_import_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialImportRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialImportJobOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/social-saves/import/{job_id}":{"get":{"tags":["social-saves"],"summary":"Poll Import Job","operationId":"poll_import_job_api_v1_social_saves_import__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SocialImportJobOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/templates":{"get":{"tags":["collections"],"summary":"List Templates","description":"Return all pre-built collection templates (no auth required).","operationId":"list_templates_api_v1_collections_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CollectionTemplate"},"type":"array","title":"Response List Templates Api V1 Collections Templates Get"}}}}}}},"/api/v1/collections/from-template":{"post":{"tags":["collections"],"summary":"Create From Template","description":"Create a new collection from a template ID.","operationId":"create_from_template_api_v1_collections_from_template_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionListItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/collections":{"get":{"tags":["collections"],"summary":"List Collections","operationId":"list_collections_api_v1_collections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CollectionListItem"},"type":"array","title":"Response List Collections Api V1 Collections Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["collections"],"summary":"Create Collection","operationId":"create_collection_api_v1_collections_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/collections/reorder":{"patch":{"tags":["collections"],"summary":"Reorder Collections","operationId":"reorder_collections_api_v1_collections_reorder_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionReorderRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/collections/public/{token}":{"get":{"tags":["collections"],"summary":"Get Public Collection","operationId":"get_public_collection_api_v1_collections_public__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}":{"get":{"tags":["collections"],"summary":"Get Collection","operationId":"get_collection_api_v1_collections__collection_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["collections"],"summary":"Update Collection","operationId":"update_collection_api_v1_collections__collection_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Delete Collection","operationId":"delete_collection_api_v1_collections__collection_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}/items":{"post":{"tags":["collections"],"summary":"Add Item","operationId":"add_item_api_v1_collections__collection_id__items_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionItemCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}/items/{place_id}":{"patch":{"tags":["collections"],"summary":"Update Item","operationId":"update_item_api_v1_collections__collection_id__items__place_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}},{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionItemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["collections"],"summary":"Remove Item","operationId":"remove_item_api_v1_collections__collection_id__items__place_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}},{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}/reorder":{"patch":{"tags":["collections"],"summary":"Reorder Items","operationId":"reorder_items_api_v1_collections__collection_id__reorder_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionItemReorderRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}/publish":{"post":{"tags":["collections"],"summary":"Publish Collection","operationId":"publish_collection_api_v1_collections__collection_id__publish_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/collections/{collection_id}/unpublish":{"post":{"tags":["collections"],"summary":"Unpublish Collection","operationId":"unpublish_collection_api_v1_collections__collection_id__unpublish_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Collection Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/trip-memories/generate-recap":{"post":{"tags":["trip-memories"],"summary":"Generate Recap","description":"Generate an AI recap — returns text only, nothing is saved.\n\nResolves stop names by trying (in order):\n1. generated_plans table (NL planner plans stored as JSON)\n2. plans table (rule-based planner)\n3. client-provided stop_names fallback","operationId":"generate_recap_api_v1_trip_memories_generate_recap_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRecapRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRecapResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/trip-memories":{"get":{"tags":["trip-memories"],"summary":"List Trip Memories","operationId":"list_trip_memories_api_v1_trip_memories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TripMemoryResponse"},"type":"array","title":"Response List Trip Memories Api V1 Trip Memories Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["trip-memories"],"summary":"Create Trip Memory","operationId":"create_trip_memory_api_v1_trip_memories_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripMemoryCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripMemoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/trip-memories/on-this-day":{"get":{"tags":["trip-memories"],"summary":"Memories On This Day","operationId":"memories_on_this_day_api_v1_trip_memories_on_this_day_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TripMemoryResponse"},"type":"array","title":"Response Memories On This Day Api V1 Trip Memories On This Day Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/trip-memories/{memory_id}":{"get":{"tags":["trip-memories"],"summary":"Get Trip Memory","operationId":"get_trip_memory_api_v1_trip_memories__memory_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Memory Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripMemoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["trip-memories"],"summary":"Update Trip Memory","operationId":"update_trip_memory_api_v1_trip_memories__memory_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Memory Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripMemoryUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripMemoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["trip-memories"],"summary":"Delete Trip Memory","operationId":"delete_trip_memory_api_v1_trip_memories__memory_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Memory Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/tags":{"get":{"tags":["tags"],"summary":"List User Tags","description":"Return all tags used by the current user with their usage counts.","operationId":"list_user_tags_api_v1_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TagWithCount"},"type":"array","title":"Response List User Tags Api V1 Tags Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/tags/vocabulary":{"get":{"tags":["tags"],"summary":"Get Vocabulary","description":"Return the suggested tag vocabulary grouped by category.","operationId":"get_vocabulary_api_v1_tags_vocabulary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Response Get Vocabulary Api V1 Tags Vocabulary Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/tags/suggest/{place_id}":{"get":{"tags":["tags"],"summary":"Suggest Tags","description":"Return AI-generated tag suggestions and the full vocabulary for a place.","operationId":"suggest_tags_api_v1_tags_suggest__place_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagSuggestionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/tags":{"post":{"tags":["tags"],"summary":"Add Tags","description":"Normalise the supplied tags and merge them into the place's user_tags.\n\nDuplicate tags (after normalisation) are silently ignored.\nReturns the updated PlaceResponse.","operationId":"add_tags_api_v1_places__place_id__tags_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddTagsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/places/{place_id}/tags/{tag}":{"delete":{"tags":["tags"],"summary":"Remove Tag","description":"Remove a single tag from the place's user_tags list.\n\nThe tag path parameter is matched after normalisation so URL-encoded\nforms work transparently. Returns the updated PlaceResponse.","operationId":"remove_tag_api_v1_places__place_id__tags__tag__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"place_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Place Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","title":"Tag"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/privacy/dashboard":{"get":{"tags":["privacy"],"summary":"Get Dashboard","description":"Return a full privacy dashboard for the authenticated user.\n\nIncludes consent status for every data source plus aggregate counts\nfor places, visits, and plans.","operationId":"get_dashboard_api_v1_privacy_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataDashboardResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/privacy/consents":{"get":{"tags":["privacy"],"summary":"List Consents","description":"Return consent status for all data sources.","operationId":"list_consents_api_v1_privacy_consents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ConsentResponse"},"type":"array","title":"Response List Consents Api V1 Privacy Consents Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/privacy/consents/{source_type}/grant":{"post":{"tags":["privacy"],"summary":"Grant Consent","description":"Enable a data source for this user.\n\nRecords the current privacy policy version alongside the consent timestamp.\nReturns 400 if the source is ALWAYS_ON (cannot be toggled).","operationId":"grant_consent_api_v1_privacy_consents__source_type__grant_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/DataSourceType"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/privacy/consents/{source_type}/revoke":{"post":{"tags":["privacy"],"summary":"Revoke Consent","description":"Disable a data source for this user.\n\nStops future data collection but does NOT delete existing records.\nUse DELETE /privacy/data/{source_type} to delete stored data.\nReturns 400 if the source is ALWAYS_ON.","operationId":"revoke_consent_api_v1_privacy_consents__source_type__revoke_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/DataSourceType"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/privacy/data/{source_type}":{"delete":{"tags":["privacy"],"summary":"Delete Source Data","description":"Delete all stored records from a single data source.\n\nWhen preserve_edited=True (default), Place records that the user has\npersonalised (added a rating, notes, or tags) are kept intact.\nSet preserve_edited=False to delete everything regardless.\n\nDoes NOT revoke consent — use POST /privacy/consents/{source_type}/revoke\nto stop future collection.","operationId":"delete_source_data_api_v1_privacy_data__source_type__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source_type","in":"path","required":true,"schema":{"$ref":"#/components/schemas/DataSourceType"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_delete_source_data_api_v1_privacy_data__source_type__delete"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/privacy/data/all":{"delete":{"tags":["privacy"],"summary":"Delete All Data","description":"Delete ALL user data across every source.\n\nThe User account itself is not deleted; use DELETE /users/me for full\naccount erasure. preserve_edited is always False for this operation.","operationId":"delete_all_data_api_v1_privacy_data_all_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAllResult"}}}}},"security":[{"HTTPBearer":[]}]}},"/health":{"get":{"tags":["system"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"AddTagsRequest":{"properties":{"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["tags"],"title":"AddTagsRequest"},"AlternativeStop":{"properties":{"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"},"why":{"type":"string","title":"Why"}},"type":"object","required":["name","category","why"],"title":"AlternativeStop","description":"A backup option suggested alongside a primary plan stop."},"ApifyUsageStatsResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"slug":{"type":"string","title":"Slug"},"apify_enabled":{"type":"boolean","title":"Apify Enabled"},"apify_daily_limit":{"type":"integer","title":"Apify Daily Limit"},"period_days":{"type":"integer","title":"Period Days"},"total_runs":{"type":"integer","title":"Total Runs"},"successful_runs":{"type":"integer","title":"Successful Runs"},"success_rate":{"type":"number","title":"Success Rate"},"total_cost_usd":{"type":"string","title":"Total Cost Usd"}},"type":"object","required":["tenant_id","slug","apify_enabled","apify_daily_limit","period_days","total_runs","successful_runs","success_rate","total_cost_usd"],"title":"ApifyUsageStatsResponse"},"AppVersionResponse":{"properties":{"min_supported_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Min Supported Version"},"latest_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Version"}},"type":"object","required":["min_supported_version","latest_version"],"title":"AppVersionResponse"},"AttractionCandidate":{"properties":{"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"},"lat":{"type":"number","title":"Lat"},"lon":{"type":"number","title":"Lon"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"is_free":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Free"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"}},"type":"object","required":["name","category","lat","lon"],"title":"AttractionCandidate","description":"A candidate place returned by POST /planner/discover-attractions."},"B2BBrandingResponse":{"properties":{"display_name":{"type":"string","title":"Display Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"accent_color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accent Color"}},"type":"object","required":["display_name","logo_url","accent_color"],"title":"B2BBrandingResponse","description":"Response for GET /b2b/branding — consumed by the embeddable widget."},"B2BInsightsResponse":{"properties":{"tenant":{"type":"string","title":"Tenant"},"period_days":{"type":"integer","title":"Period Days"},"total_plans":{"type":"integer","title":"Total Plans"},"top_moods":{"items":{"$ref":"#/components/schemas/InsightBucket"},"type":"array","title":"Top Moods"},"top_place_types":{"items":{"$ref":"#/components/schemas/InsightBucket"},"type":"array","title":"Top Place Types"},"companion_breakdown":{"items":{"$ref":"#/components/schemas/InsightBucket"},"type":"array","title":"Companion Breakdown"}},"type":"object","required":["tenant","period_days","total_plans","top_moods","top_place_types","companion_breakdown"],"title":"B2BInsightsResponse","description":"Response for GET /b2b/insights."},"B2BPlannerRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query"},"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon"},"radius_km":{"type":"number","maximum":100.0,"exclusiveMinimum":0.0,"title":"Radius Km","default":5.0},"travel_mode":{"type":"string","pattern":"^(driving|walking|cycling)$","title":"Travel Mode","default":"walking"},"companion":{"type":"string","title":"Companion","default":"solo"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"language":{"type":"string","maxLength":20,"minLength":2,"title":"Language","default":"en"}},"type":"object","required":["query","lat","lon"],"title":"B2BPlannerRequest","description":"Request body for POST /b2b/planner/query."},"B2BPlannerResponse":{"properties":{"query":{"type":"string","title":"Query"},"intent":{"$ref":"#/components/schemas/PlannerIntent"},"plans":{"items":{"$ref":"#/components/schemas/Plan"},"type":"array","title":"Plans"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["query","intent","plans","weather","generated_at"],"title":"B2BPlannerResponse","description":"Response for POST /b2b/planner/query."},"B2BUsageResponse":{"properties":{"tenant":{"type":"string","title":"Tenant"},"plan_limit_per_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plan Limit Per Month"},"used_this_month":{"type":"integer","title":"Used This Month"},"remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Remaining"}},"type":"object","required":["tenant","plan_limit_per_month","used_this_month","remaining"],"title":"B2BUsageResponse","description":"Response for GET /b2b/usage."},"Body_delete_source_data_api_v1_privacy_data__source_type__delete":{"properties":{"preserve_edited":{"type":"boolean","title":"Preserve Edited","default":true}},"type":"object","title":"Body_delete_source_data_api_v1_privacy_data__source_type__delete"},"Body_import_google_takeout_api_v1_places_import_google_takeout_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_import_google_takeout_api_v1_places_import_google_takeout_post"},"Body_import_place_from_image_api_v1_places_import_from_image_post":{"properties":{"image":{"type":"string","contentMediaType":"application/octet-stream","title":"Image"}},"type":"object","required":["image"],"title":"Body_import_place_from_image_api_v1_places_import_from_image_post"},"CampaignResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"title":{"type":"string","title":"Title"},"subtitle":{"type":"string","title":"Subtitle"},"active_from":{"type":"string","format":"date-time","title":"Active From"},"active_to":{"type":"string","format":"date-time","title":"Active To"},"preset_chips":{"items":{"type":"string"},"type":"array","title":"Preset Chips"}},"type":"object","required":["slug","title","subtitle","active_from","active_to","preset_chips"],"title":"CampaignResponse","description":"The campaign currently live — what the app's home card renders from."},"CampaignVenue":{"properties":{"place_id":{"type":"string","title":"Place Id"},"name":{"type":"string","title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"type":"number","title":"Lat"},"lng":{"type":"number","title":"Lng"},"distance_km":{"type":"number","title":"Distance Km"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"user_rating_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Rating Count"},"website_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website Url"},"opening_hours":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Opening Hours"},"likely_car_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Likely Car Only"},"nearest_station_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Nearest Station Km"},"is_pinned":{"type":"boolean","title":"Is Pinned","default":false}},"type":"object","required":["place_id","name","lat","lng","distance_km"],"title":"CampaignVenue","description":"One candidate anchor venue."},"CampaignVenuesResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"venues":{"items":{"$ref":"#/components/schemas/CampaignVenue"},"type":"array","title":"Venues"}},"type":"object","required":["slug","venues"],"title":"CampaignVenuesResponse"},"ChildIn":{"properties":{"age_range":{"type":"string","enum":["0-2","3-5","6-11","12-17"],"title":"Age Range"},"label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Label"}},"type":"object","required":["age_range"],"title":"ChildIn"},"ChildOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"age_range":{"type":"string","title":"Age Range"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","required":["id","age_range","label"],"title":"ChildOut"},"CollectionCreate":{"properties":{"name":{"type":"string","title":"Name"},"emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emoji"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"CollectionCreate"},"CollectionItemCreate":{"properties":{"place_id":{"type":"string","format":"uuid","title":"Place Id"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["place_id"],"title":"CollectionItemCreate"},"CollectionItemOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"place_id":{"type":"string","format":"uuid","title":"Place Id"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"added_at":{"type":"string","format":"date-time","title":"Added At"},"sort_order":{"type":"integer","title":"Sort Order"},"place":{"$ref":"#/components/schemas/PlaceListItem"}},"type":"object","required":["id","place_id","note","added_at","sort_order","place"],"title":"CollectionItemOut"},"CollectionItemReorderRequest":{"properties":{"ordered_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Ordered Ids"}},"type":"object","required":["ordered_ids"],"title":"CollectionItemReorderRequest"},"CollectionItemResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"place_id":{"type":"string","format":"uuid","title":"Place Id"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"added_at":{"type":"string","format":"date-time","title":"Added At"},"sort_order":{"type":"integer","title":"Sort Order"}},"type":"object","required":["id","place_id","note","added_at","sort_order"],"title":"CollectionItemResponse"},"CollectionItemUpdate":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"CollectionItemUpdate"},"CollectionListItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emoji"},"is_default":{"type":"boolean","title":"Is Default"},"is_public":{"type":"boolean","title":"Is Public"},"place_count":{"type":"integer","title":"Place Count"},"cover_photos":{"items":{"type":"string"},"type":"array","title":"Cover Photos"},"share_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Token"},"sort_order":{"type":"integer","title":"Sort Order"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","description","emoji","is_default","is_public","place_count","cover_photos","share_token","sort_order","created_at","updated_at"],"title":"CollectionListItem"},"CollectionReorderRequest":{"properties":{"ordered_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Ordered Ids"}},"type":"object","required":["ordered_ids"],"title":"CollectionReorderRequest"},"CollectionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emoji"},"is_default":{"type":"boolean","title":"Is Default"},"is_public":{"type":"boolean","title":"Is Public"},"share_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Token"},"cover_photos":{"items":{"type":"string"},"type":"array","title":"Cover Photos"},"items":{"items":{"$ref":"#/components/schemas/CollectionItemOut"},"type":"array","title":"Items"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","description","emoji","is_default","is_public","share_token","cover_photos","items","created_at","updated_at"],"title":"CollectionResponse"},"CollectionTemplate":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"emoji":{"type":"string","title":"Emoji"},"description":{"type":"string","title":"Description"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"companion":{"type":"string","title":"Companion"},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"}},"type":"object","required":["id","name","emoji","description","tags","companion","vibes"],"title":"CollectionTemplate"},"CollectionUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emoji"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","title":"CollectionUpdate"},"CompUserRequest":{"properties":{"email":{"type":"string","title":"Email"},"mode":{"type":"string","enum":["tester","lifetime","until","revoke"],"title":"Mode"},"days":{"anyOf":[{"type":"integer","maximum":3650.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Days"},"until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Until"}},"type":"object","required":["email","mode"],"title":"CompUserRequest","description":"Grant or revoke free Plus access for a user, by email.\n\nmodes:\n  - \"tester\":   is_tester=True — permanent free access (team / internal).\n  - \"lifetime\": is_lifetime=True — gifted lifetime access.\n  - \"until\":    plus_until set to a future date — time-limited comp (e.g.\n                a Product Hunt winner gets 1 year). Provide `days` OR `until`.\n  - \"revoke\":   clear all three (is_tester=False, is_lifetime=False,\n                plus_until=None) — remove any comp granted here.\n\nNote: this sets Yondry's own entitlement flags directly. It does NOT create\na RevenueCat entitlement — for externally-visible comps that should show in\nthe RC dashboard, grant a promotional entitlement in RC instead. Both make\n`_is_plus()` return true; this is the fast in-house path."},"CompUserResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"is_tester":{"type":"boolean","title":"Is Tester"},"is_lifetime":{"type":"boolean","title":"Is Lifetime"},"plus_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Plus Until"}},"type":"object","required":["user_id","email","is_tester","is_lifetime","plus_until"],"title":"CompUserResponse"},"CompanionIn":{"properties":{"type":{"type":"string","enum":["adult","child"],"title":"Type"},"age_range":{"anyOf":[{"type":"string","enum":["0-2","3-5","6-11","12-17"]},{"type":"null"}],"title":"Age Range"}},"type":"object","required":["type"],"title":"CompanionIn"},"ConsentResponse":{"properties":{"source_type":{"$ref":"#/components/schemas/DataSourceType"},"is_enabled":{"type":"boolean","title":"Is Enabled"},"consent_tier":{"$ref":"#/components/schemas/ConsentTier"},"consented_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consented At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"},"consent_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent Version"},"record_count":{"type":"integer","title":"Record Count"},"oldest_record_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Oldest Record At"},"newest_record_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Newest Record At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"retention_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retention Days"}},"type":"object","required":["source_type","is_enabled","consent_tier","consented_at","revoked_at","consent_version","record_count","oldest_record_at","newest_record_at","last_used_at","retention_days"],"title":"ConsentResponse"},"ConsentTier":{"type":"string","enum":["implicit","explicit","always_on"],"title":"ConsentTier"},"CreateFeedbackRequest":{"properties":{"rating":{"type":"string","enum":["loved","ok","never_again"],"title":"Rating"},"notes":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Notes"}},"type":"object","required":["rating"],"title":"CreateFeedbackRequest"},"CreateTenantRequest":{"properties":{"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"plan_limit_per_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plan Limit Per Month"}},"type":"object","required":["name","slug"],"title":"CreateTenantRequest"},"CreateTenantResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"name":{"type":"string","title":"Name"},"slug":{"type":"string","title":"Slug"},"api_key":{"type":"string","title":"Api Key"},"plan_limit_per_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plan Limit Per Month"}},"type":"object","required":["tenant_id","name","slug","api_key","plan_limit_per_month"],"title":"CreateTenantResponse"},"CreateVisitRequest":{"properties":{"place_id":{"type":"string","format":"uuid","title":"Place Id"},"visited_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Visited At"}},"type":"object","required":["place_id"],"title":"CreateVisitRequest","description":"Backward-compatible simple check-in request (used by PlaceDetailScreen)."},"DataDashboardResponse":{"properties":{"consents":{"items":{"$ref":"#/components/schemas/ConsentResponse"},"type":"array","title":"Consents"},"total_places":{"type":"integer","title":"Total Places"},"total_visits":{"type":"integer","title":"Total Visits"},"total_plans":{"type":"integer","title":"Total Plans"},"account_created_at":{"type":"string","format":"date-time","title":"Account Created At"},"privacy_policy_version":{"type":"string","title":"Privacy Policy Version"}},"type":"object","required":["consents","total_places","total_visits","total_plans","account_created_at","privacy_policy_version"],"title":"DataDashboardResponse"},"DataSourceType":{"type":"string","enum":["google_maps_takeout","share_extension","social_save","link_unfurl","image_ocr","device_location","visit_history","plan_history"],"title":"DataSourceType"},"DeleteAllResult":{"properties":{"results":{"items":{"$ref":"#/components/schemas/DeletionResult"},"type":"array","title":"Results"},"total_deleted":{"type":"integer","title":"Total Deleted"},"message":{"type":"string","title":"Message"}},"type":"object","required":["results","total_deleted","message"],"title":"DeleteAllResult"},"DeletionResult":{"properties":{"source_type":{"$ref":"#/components/schemas/DataSourceType"},"deleted_count":{"type":"integer","title":"Deleted Count"},"preserved_count":{"type":"integer","title":"Preserved Count"},"preserved_place_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Preserved Place Ids"},"message":{"type":"string","title":"Message"}},"type":"object","required":["source_type","deleted_count","preserved_count","preserved_place_ids","message"],"title":"DeletionResult"},"DemoPartnerStats":{"properties":{"partner":{"type":"string","title":"Partner"},"views":{"type":"integer","title":"Views"},"unique_visitors":{"type":"integer","title":"Unique Visitors"},"plans":{"type":"integer","title":"Plans"},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen"}},"type":"object","required":["partner","views","unique_visitors","plans","first_seen","last_seen"],"title":"DemoPartnerStats"},"DemoPlanRequest":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon"},"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query"},"transport":{"type":"string","pattern":"^(driving|walking|cycling|tube)$","title":"Transport","default":"walking"},"radius_m":{"type":"number","maximum":50000.0,"exclusiveMinimum":0.0,"title":"Radius M","default":2000.0},"partner":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Partner"},"session_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Session Id"}},"type":"object","required":["lat","lon","query"],"title":"DemoPlanRequest"},"DemoPlanResponse":{"properties":{"query":{"type":"string","title":"Query"},"intent":{"$ref":"#/components/schemas/PlannerIntent"},"plans":{"items":{"$ref":"#/components/schemas/Plan"},"type":"array","title":"Plans"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["query","intent","plans","weather","generated_at"],"title":"DemoPlanResponse"},"DemoStatsResponse":{"properties":{"window_days":{"type":"integer","title":"Window Days"},"since":{"type":"string","title":"Since"},"totals":{"additionalProperties":true,"type":"object","title":"Totals"},"partners":{"items":{"$ref":"#/components/schemas/DemoPartnerStats"},"type":"array","title":"Partners"}},"type":"object","required":["window_days","since","totals","partners"],"title":"DemoStatsResponse"},"DemoTrackRequest":{"properties":{"partner":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Partner"},"event":{"type":"string","const":"view","title":"Event","default":"view"},"session_id":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Session Id"},"referrer":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Referrer"}},"type":"object","title":"DemoTrackRequest","description":"Beacon sent when the demo page loads.\n\nOnly \"view\" is accepted here — a plan is recorded by the plan route itself,\nso the client cannot inflate the number that matters."},"DiscoverAttractionsRequest":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lon":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lon"},"radius_km":{"type":"number","maximum":200.0,"exclusiveMinimum":0.0,"title":"Radius Km","default":20.0},"companion":{"type":"string","title":"Companion","default":"solo"},"query":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Query"}},"type":"object","required":["lat","lon"],"title":"DiscoverAttractionsRequest","description":"Request body for POST /planner/discover-attractions."},"DiscoverAttractionsResponse":{"properties":{"suggest":{"type":"boolean","title":"Suggest"},"candidates":{"items":{"$ref":"#/components/schemas/AttractionCandidate"},"type":"array","title":"Candidates"}},"type":"object","required":["suggest","candidates"],"title":"DiscoverAttractionsResponse","description":"Response for POST /planner/discover-attractions."},"DiscoveryConstraints":{"properties":{"max_travel_minutes":{"type":"integer","title":"Max Travel Minutes"},"vibe_keywords":{"items":{"type":"string"},"type":"array","title":"Vibe Keywords"},"must_haves":{"items":{"type":"string"},"type":"array","title":"Must Haves"},"exclusions":{"items":{"type":"string"},"type":"array","title":"Exclusions"}},"type":"object","required":["max_travel_minutes"],"title":"DiscoveryConstraints","description":"Spatial/vibe constraints when the user wants us to find a destination."},"ExtractionLayerStat":{"properties":{"extraction_layer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extraction Layer"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["extraction_layer","count"],"title":"ExtractionLayerStat"},"ExtractionTraceOut":{"properties":{"place_id":{"type":"string","format":"uuid","title":"Place Id"},"social_save_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Social Save Id"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"extraction_layer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extraction Layer"},"extraction_attempts":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Extraction Attempts"},"caption_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption Excerpt"},"progress_events":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Progress Events"},"place_name":{"type":"string","title":"Place Name"},"place_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Place Address"},"place_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Place Lat"},"place_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Place Lon"}},"type":"object","required":["place_id","place_name"],"title":"ExtractionTraceOut","description":"Dev-only extraction trace for a socially-saved place.\n\nPopulated by GET /places/{id}/extraction-trace.\nReturns None fields for places not saved via the social pipeline."},"FeedbackOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"place_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Place Id"},"rating":{"type":"string","title":"Rating"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","place_id","rating","notes","created_at"],"title":"FeedbackOut"},"GenerateGuideRequest":{"properties":{"name":{"type":"string","title":"Name"},"city":{"type":"string","title":"City"},"lat":{"type":"number","title":"Lat"},"lon":{"type":"number","title":"Lon"},"place_queries":{"items":{"type":"string"},"type":"array","maxItems":20,"minItems":1,"title":"Place Queries"},"radius_m":{"type":"number","maximum":50000.0,"exclusiveMinimum":0.0,"title":"Radius M","default":2000.0}},"type":"object","required":["name","city","lat","lon","place_queries"],"title":"GenerateGuideRequest"},"GenerateGuideResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"city":{"type":"string","title":"City"},"highlight_count":{"type":"integer","title":"Highlight Count"},"dropped_count":{"type":"integer","title":"Dropped Count"}},"type":"object","required":["slug","name","city","highlight_count","dropped_count"],"title":"GenerateGuideResponse"},"GenerateRecapRequest":{"properties":{"plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Plan Id"},"plan_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Name"},"stop_names":{"items":{"type":"string"},"type":"array","title":"Stop Names","default":[]},"stop_ratings":{"additionalProperties":true,"type":"object","title":"Stop Ratings","default":{}},"highlight_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Highlight Note"},"mood_tag":{"$ref":"#/components/schemas/MoodTag"}},"type":"object","required":["mood_tag"],"title":"GenerateRecapRequest"},"GenerateRecapResponse":{"properties":{"recap":{"type":"string","title":"Recap"}},"type":"object","required":["recap"],"title":"GenerateRecapResponse"},"GenerateScenariosResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"scenarios_generated":{"items":{"type":"string"},"type":"array","title":"Scenarios Generated"},"scenarios_failed":{"items":{"type":"string"},"type":"array","title":"Scenarios Failed"}},"type":"object","required":["slug","scenarios_generated","scenarios_failed"],"title":"GenerateScenariosResponse"},"GeneratedPlanDetail":{"properties":{"generated_plan_id":{"type":"string","format":"uuid","title":"Generated Plan Id"},"query":{"type":"string","title":"Query"},"plans":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Plans"},"intent":{"additionalProperties":true,"type":"object","title":"Intent"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback"},"saved":{"type":"boolean","title":"Saved"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["generated_plan_id","query","plans","intent","weather","feedback","saved","generated_at"],"title":"GeneratedPlanDetail","description":"One stored plan, in full.\n\nDeliberately mirrors the shape of the query response, down to the\n`generated_plan_id` field name, so the app renders a past day with the\nrenderer it already has rather than growing a second one that drifts from\nthe first.\n\n`plans` is typed loosely on purpose. These rows go back to the app's first\nweeks and the plan shape has changed since; validating them against today's\nschema would turn an old day into a 500 rather than a slightly sparse page.\nThe client already tolerates missing fields."},"GeneratedPlanSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"query":{"type":"string","title":"Query"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"stop_names":{"items":{"type":"string"},"type":"array","title":"Stop Names"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"plan_count":{"type":"integer","title":"Plan Count"},"feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback"},"saved":{"type":"boolean","title":"Saved"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["id","query","weather","plan_count","feedback","saved","generated_at"],"title":"GeneratedPlanSummary","description":"Lightweight history list item."},"GoogleTakeoutImportResult":{"properties":{"imported":{"type":"integer","title":"Imported"},"skipped":{"type":"integer","title":"Skipped"},"failed":{"type":"integer","title":"Failed"},"total":{"type":"integer","title":"Total","default":0}},"type":"object","required":["imported","skipped","failed"],"title":"GoogleTakeoutImportResult"},"GuestUpgradeResponse":{"properties":{"user":{"$ref":"#/components/schemas/UserAuthInfo"}},"type":"object","required":["user"],"title":"GuestUpgradeResponse","description":"Result of attaching a real identity to the caller's guest account.\n\nNo token pair: the row keeps its id, so the caller's existing tokens stay\nvalid and re-issuing would only invalidate a session that is still correct."},"GuideHighlight":{"properties":{"name":{"type":"string","title":"Name"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"why":{"type":"string","title":"Why"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"}},"type":"object","required":["name","category","why","address","lat","lon","google_place_id","rating"],"title":"GuideHighlight"},"GuideResponse":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"city":{"type":"string","title":"City"},"lat":{"type":"number","title":"Lat"},"lon":{"type":"number","title":"Lon"},"intro":{"type":"string","title":"Intro"},"highlights":{"items":{"$ref":"#/components/schemas/GuideHighlight"},"type":"array","title":"Highlights"},"scenario_plans":{"items":{"$ref":"#/components/schemas/GuideScenario"},"type":"array","title":"Scenario Plans"}},"type":"object","required":["slug","name","city","lat","lon","intro","highlights","scenario_plans"],"title":"GuideResponse"},"GuideScenario":{"properties":{"scenario":{"type":"string","title":"Scenario"},"label":{"type":"string","title":"Label"},"plan":{"$ref":"#/components/schemas/Plan"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"}},"type":"object","required":["scenario","label","plan","weather"],"title":"GuideScenario"},"GuideSummary":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"city":{"type":"string","title":"City"}},"type":"object","required":["slug","name","city"],"title":"GuideSummary"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImportFromUrlRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url","description":"Google Maps URL to import (full, short, or CID format)"}},"type":"object","required":["url"],"title":"ImportFromUrlRequest"},"InsightBucket":{"properties":{"label":{"type":"string","title":"Label"},"count":{"type":"integer","title":"Count"},"pct":{"type":"number","title":"Pct"}},"type":"object","required":["label","count","pct"],"title":"InsightBucket"},"JourneySegment":{"properties":{"from_location":{"type":"string","title":"From Location"},"to_location":{"type":"string","title":"To Location"},"mode":{"type":"string","title":"Mode"},"minutes":{"type":"integer","title":"Minutes"},"depart_time":{"type":"string","format":"date-time","title":"Depart Time"}},"type":"object","required":["from_location","to_location","mode","minutes","depart_time"],"title":"JourneySegment","description":"Arrival or return leg between the user's start location and the destination zone."},"LaunchMetricsResponse":{"properties":{"generated_at":{"type":"string","title":"Generated At"},"window":{"additionalProperties":true,"type":"object","title":"Window"},"totals":{"additionalProperties":true,"type":"object","title":"Totals"},"window_summary":{"additionalProperties":true,"type":"object","title":"Window Summary"},"this_month":{"additionalProperties":true,"type":"object","title":"This Month"},"first_plan":{"additionalProperties":true,"type":"object","title":"First Plan"},"saving":{"additionalProperties":true,"type":"object","title":"Saving"},"prompts":{"additionalProperties":true,"type":"object","title":"Prompts"},"pages":{"additionalProperties":true,"type":"object","title":"Pages"},"daily":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Daily"}},"type":"object","required":["generated_at","window","totals","window_summary","this_month","first_plan","saving","prompts","pages","daily"],"title":"LaunchMetricsResponse"},"ListMeta":{"properties":{"total":{"type":"integer","title":"Total"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["total"],"title":"ListMeta","description":"Envelope metadata included with every list response."},"ListResponse_PlaceListItem_":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PlaceListItem"},"type":"array","title":"Data"},"meta":{"$ref":"#/components/schemas/ListMeta"}},"type":"object","required":["data","meta"],"title":"ListResponse[PlaceListItem]"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MoodTag":{"type":"string","enum":["joyful","cosy","adventurous","restorative"],"title":"MoodTag"},"MustIncludePlace":{"properties":{"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"},"lat":{"type":"number","title":"Lat"},"lon":{"type":"number","title":"Lon"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"is_free":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Free"}},"type":"object","required":["name","category","lat","lon"],"title":"MustIncludePlace","description":"A place the user explicitly wants included in every generated plan."},"PlaceCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"place_type":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}]},"source_type":{"$ref":"#/components/schemas/SourceType","default":"manual"},"address":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Lon"},"city":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"City"},"country_code":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"Country Code"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"user_notes":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"User Notes"},"user_tags":{"items":{"type":"string","maxLength":100},"type":"array","title":"User Tags"},"is_favourite":{"type":"boolean","title":"Is Favourite","default":false},"source_url":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Source Url"},"category":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Category"},"indoor_outdoor":{"anyOf":[{"type":"string","enum":["indoor","outdoor","both"]},{"type":"null"}],"title":"Indoor Outdoor"},"budget_level":{"anyOf":[{"type":"string","enum":["free","low","medium","high"]},{"type":"null"}],"title":"Budget Level"},"kid_min_age":{"anyOf":[{"type":"integer","maximum":17.0,"minimum":0.0},{"type":"null"}],"title":"Kid Min Age"},"kid_max_age":{"anyOf":[{"type":"integer","maximum":17.0,"minimum":0.0},{"type":"null"}],"title":"Kid Max Age"},"always_open":{"type":"boolean","title":"Always Open","default":false},"opening_time":{"anyOf":[{"type":"string","pattern":"^\\d{2}:\\d{2}$"},{"type":"null"}],"title":"Opening Time"},"closing_time":{"anyOf":[{"type":"string","pattern":"^\\d{2}:\\d{2}$"},{"type":"null"}],"title":"Closing Time"},"tags":{"items":{"type":"string","maxLength":100},"type":"array","maxItems":50,"title":"Tags"},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name"],"title":"PlaceCreate","description":"Fields for creating a place manually via POST /places.\n\nplace_type and source_type are recommended but optional so that existing\nmobile API calls (which omit them) continue to work without changes."},"PlaceData":{"properties":{"name":{"type":"string","title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"}},"type":"object","required":["name"],"title":"PlaceData","description":"Minimal place candidate as returned by the AI extraction step."},"PlaceListItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"place_type":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"price_level":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Level"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"photos":{"items":{"type":"string"},"type":"array","title":"Photos"},"photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Url"},"is_favourite":{"type":"boolean","title":"Is Favourite"},"status":{"$ref":"#/components/schemas/PlaceStatus"},"permanently_closed":{"type":"boolean","title":"Permanently Closed"},"source_type":{"anyOf":[{"$ref":"#/components/schemas/SourceType"},{"type":"null"}]},"source":{"type":"string","title":"Source"},"user_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Rating"},"user_tags":{"items":{"type":"string"},"type":"array","title":"User Tags"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"saved_at":{"type":"string","format":"date-time","title":"Saved At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"is_deleted":{"type":"boolean","title":"Is Deleted"},"stroller_friendly":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stroller Friendly"},"has_changing_facilities":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Changing Facilities"},"noise_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Noise Level"},"is_open_now":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Open Now"},"visit_count":{"type":"integer","title":"Visit Count","default":0},"distance_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Km"}},"type":"object","required":["id","name","address","lat","lon","city","country_code","place_type","category","price_level","rating","photos","is_favourite","status","permanently_closed","source_type","source","user_rating","user_tags","tags","saved_at","created_at","updated_at","deleted_at","is_deleted"],"title":"PlaceListItem","description":"Compact schema for list and map views.\n\nopening_hours detail is omitted; photos is trimmed to the first entry by\nthe route handler before serialisation."},"PlaceOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"indoor_outdoor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Indoor Outdoor"},"budget_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Budget Level"},"kid_min_age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Kid Min Age"},"kid_max_age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Kid Max Age"},"always_open":{"type":"boolean","title":"Always Open"},"opening_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opening Time"},"closing_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closing Time"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"source":{"type":"string","title":"Source"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"is_deleted":{"type":"boolean","title":"Is Deleted"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","address","lat","lon","category","indoor_outdoor","budget_level","kid_min_age","kid_max_age","always_open","opening_time","closing_time","tags","source","notes","is_deleted","created_at","updated_at"],"title":"PlaceOut","description":"Legacy response schema retained for places_import.py and existing tests.\n\nNew endpoints return PlaceResponse.  This type is intentionally narrow so\nit deserialises cleanly from both old and new Place ORM rows."},"PlaceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"place_type":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}]},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"google_types":{"items":{"type":"string"},"type":"array","title":"Google Types"},"price_level":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Level"},"rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rating"},"rating_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating Count"},"opening_hours":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Opening Hours"},"website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"photos":{"items":{"type":"string"},"type":"array","title":"Photos"},"photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Photo Url"},"editorial_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Editorial Summary"},"last_enriched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Enriched At"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"user_rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"User Rating"},"user_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Notes"},"user_tags":{"items":{"type":"string"},"type":"array","title":"User Tags"},"is_favourite":{"type":"boolean","title":"Is Favourite"},"status":{"$ref":"#/components/schemas/PlaceStatus"},"permanently_closed":{"type":"boolean","title":"Permanently Closed"},"source_type":{"anyOf":[{"$ref":"#/components/schemas/SourceType"},{"type":"null"}]},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"source_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Source Id"},"source":{"type":"string","title":"Source"},"saved_at":{"type":"string","format":"date-time","title":"Saved At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"is_deleted":{"type":"boolean","title":"Is Deleted"},"stroller_friendly":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stroller Friendly"},"has_changing_facilities":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Changing Facilities"},"noise_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Noise Level"},"indoor_outdoor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Indoor Outdoor"},"budget_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Budget Level"},"kid_min_age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Kid Min Age"},"kid_max_age":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Kid Max Age"},"always_open":{"type":"boolean","title":"Always Open"},"opening_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opening Time"},"closing_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closing Time"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"is_open_now":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Open Now"},"visit_count":{"type":"integer","title":"Visit Count","default":0},"distance_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Km"}},"type":"object","required":["id","name","address","lat","lon","city","country_code","place_type","category","google_place_id","google_types","price_level","rating","rating_count","opening_hours","website","phone","photos","editorial_summary","last_enriched_at","user_id","user_rating","user_notes","user_tags","is_favourite","status","permanently_closed","source_type","source_url","source_id","source","saved_at","created_at","updated_at","deleted_at","is_deleted","indoor_outdoor","budget_level","kid_min_age","kid_max_age","always_open","opening_time","closing_time","notes","tags"],"title":"PlaceResponse","description":"Full place response including all fields and server-computed values."},"PlaceStatus":{"type":"string","enum":["pending","active","closed"],"title":"PlaceStatus"},"PlaceType":{"type":"string","enum":["park","playground","museum","cafe","restaurant","bar","activity","shopping","other"],"title":"PlaceType"},"PlaceUpdate":{"properties":{"user_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Notes"},"user_tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"User Tags"},"user_rating":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"User Rating"},"is_favourite":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Favourite"},"place_type":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}]},"stroller_friendly":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stroller Friendly"},"has_changing_facilities":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Changing Facilities"},"noise_level":{"anyOf":[{"type":"string","enum":["quiet","moderate","loud"]},{"type":"null"}],"title":"Noise Level"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"indoor_outdoor":{"anyOf":[{"type":"string","enum":["indoor","outdoor","both"]},{"type":"null"}],"title":"Indoor Outdoor"},"budget_level":{"anyOf":[{"type":"string","enum":["free","low","medium","high"]},{"type":"null"}],"title":"Budget Level"},"kid_min_age":{"anyOf":[{"type":"integer","maximum":17.0,"minimum":0.0},{"type":"null"}],"title":"Kid Min Age"},"kid_max_age":{"anyOf":[{"type":"integer","maximum":17.0,"minimum":0.0},{"type":"null"}],"title":"Kid Max Age"},"always_open":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Always Open"},"opening_time":{"anyOf":[{"type":"string","pattern":"^\\d{2}:\\d{2}$"},{"type":"null"}],"title":"Opening Time"},"closing_time":{"anyOf":[{"type":"string","pattern":"^\\d{2}:\\d{2}$"},{"type":"null"}],"title":"Closing Time"}},"type":"object","title":"PlaceUpdate","description":"User-editable fields only.\n\nEnrichment columns (rating, opening_hours, photos, …) are never writable\nthrough this schema — use the POST /places/{id}/enrich endpoint instead."},"Plan":{"properties":{"label":{"type":"string","title":"Label"},"stops":{"items":{"$ref":"#/components/schemas/PlanStop"},"type":"array","title":"Stops"},"summary":{"type":"string","title":"Summary"},"conflicts":{"items":{"$ref":"#/components/schemas/PlanningConflictOut"},"type":"array","title":"Conflicts"},"total_duration_mins":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Duration Mins"},"plan_notes":{"type":"string","title":"Plan Notes","default":""},"arrival_segment":{"anyOf":[{"$ref":"#/components/schemas/JourneySegment"},{"type":"null"}]},"return_segment":{"anyOf":[{"$ref":"#/components/schemas/JourneySegment"},{"type":"null"}]},"buffer_minutes":{"type":"integer","title":"Buffer Minutes","default":20},"travel_note":{"type":"string","title":"Travel Note","default":""}},"type":"object","required":["label","stops","summary"],"title":"Plan","description":"One complete itinerary option returned by Claude Sonnet."},"PlanDayRequest":{"properties":{"time_window":{"$ref":"#/components/schemas/TimeWindow"},"radius_km":{"type":"number","maximum":200.0,"exclusiveMinimum":0.0,"title":"Radius Km","description":"Search radius from home location"},"companions":{"items":{"$ref":"#/components/schemas/CompanionIn"},"type":"array","minItems":1,"title":"Companions"},"mood":{"type":"string","enum":["chill","active","cultural","outdoors","shopping","foodie"],"title":"Mood"},"budget_level":{"type":"string","enum":["free","low","medium","high"],"title":"Budget Level"},"weather":{"anyOf":[{"type":"string","enum":["sunny","cloudy","rainy","windy","cold"]},{"type":"null"}],"title":"Weather"},"origin_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Origin Lat"},"origin_lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Origin Lon"}},"type":"object","required":["time_window","radius_km","companions","mood","budget_level"],"title":"PlanDayRequest"},"PlanDayResponse":{"properties":{"plan_options":{"items":{"$ref":"#/components/schemas/PlanOption"},"type":"array","title":"Plan Options"},"weather_used":{"type":"string","enum":["sunny","cloudy","rainy","windy","cold"],"title":"Weather Used"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"}},"type":"object","required":["plan_options","weather_used","generated_at"],"title":"PlanDayResponse"},"PlanEventRequest":{"properties":{"event_type":{"type":"string","enum":["swap_requested","alternative_swapped","stop_skipped","plan_started","plan_completed","plan_abandoned","book_tickets_clicked","push_prompt_shown","push_prompt_accepted","push_prompt_dismissed","upgrade_prompt_shown","upgrade_prompt_accepted","upgrade_prompt_dismissed"],"title":"Event Type"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"}},"type":"object","required":["event_type"],"title":"PlanEventRequest","description":"Client-side plan interaction event for POST /planner/{plan_id}/events."},"PlanOption":{"properties":{"option_index":{"type":"integer","title":"Option Index"},"label":{"type":"string","title":"Label"},"steps":{"items":{"$ref":"#/components/schemas/PlanStep"},"type":"array","title":"Steps"},"total_duration_minutes":{"type":"integer","title":"Total Duration Minutes"}},"type":"object","required":["option_index","label","steps","total_duration_minutes"],"title":"PlanOption"},"PlanStep":{"properties":{"place_id":{"type":"string","format":"uuid","title":"Place Id"},"title":{"type":"string","title":"Title"},"category":{"type":"string","title":"Category"},"suggested_arrival":{"type":"string","format":"date-time","title":"Suggested Arrival"},"suggested_duration_minutes":{"type":"integer","title":"Suggested Duration Minutes"},"travel_time_minutes":{"type":"integer","title":"Travel Time Minutes"},"why_suggested":{"type":"string","title":"Why Suggested"}},"type":"object","required":["place_id","title","category","suggested_arrival","suggested_duration_minutes","travel_time_minutes","why_suggested"],"title":"PlanStep"},"PlanStop":{"properties":{"place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Place Id"},"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"},"why":{"type":"string","title":"Why"},"suggested_duration_minutes":{"type":"integer","title":"Suggested Duration Minutes"},"is_discovery":{"type":"boolean","title":"Is Discovery","default":false},"is_meal":{"type":"boolean","title":"Is Meal","default":false},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lng"},"admission_price":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Admission Price"},"alternative_stops":{"items":{"$ref":"#/components/schemas/AlternativeStop"},"type":"array","title":"Alternative Stops"},"travel_leg":{"anyOf":[{"$ref":"#/components/schemas/TravelLegOut"},{"type":"null"}]},"arrival_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arrival Time"},"transition_to_next":{"anyOf":[{"$ref":"#/components/schemas/TransitionSegment"},{"type":"null"}]}},"type":"object","required":["name","category","why","suggested_duration_minutes"],"title":"PlanStop","description":"A single stop in an NL-generated plan."},"PlannerFeedbackRequest":{"properties":{"rating":{"type":"string","enum":["thumbs_up","thumbs_down"],"title":"Rating"}},"type":"object","required":["rating"],"title":"PlannerFeedbackRequest"},"PlannerIntent":{"properties":{"moods":{"items":{"type":"string"},"type":"array","title":"Moods"},"place_types":{"items":{"type":"string"},"type":"array","title":"Place Types"},"duration_hours":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Hours"},"companions":{"items":{"type":"string"},"type":"array","title":"Companions"},"time_of_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Of Day"},"budget":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Budget"},"excluded_place_types":{"items":{"type":"string"},"type":"array","title":"Excluded Place Types"},"prefer_discovery":{"type":"boolean","title":"Prefer Discovery","default":false},"vibe_conflicts":{"items":{"type":"string"},"type":"array","title":"Vibe Conflicts"},"planning_mode":{"anyOf":[{"type":"string","enum":["local","known_day_trip","discovery_day_trip","away_trip"]},{"type":"string"},{"type":"null"}],"title":"Planning Mode"},"destination":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Destination"},"discovery_constraints":{"anyOf":[{"$ref":"#/components/schemas/DiscoveryConstraints"},{"type":"null"}]},"start_location_type":{"anyOf":[{"type":"string","enum":["home","custom","current_location","future_location"]},{"type":"string"},{"type":"null"}],"title":"Start Location Type"},"arrival_transport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arrival Transport"},"local_transport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Transport"},"duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Minutes"},"start_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Time"},"must_include_meal":{"type":"boolean","title":"Must Include Meal","default":false},"constraints_inferred":{"items":{"type":"string"},"type":"array","title":"Constraints Inferred"},"ambiguity_flags":{"items":{"type":"string"},"type":"array","title":"Ambiguity Flags"},"first_time_visitor":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"First Time Visitor"},"named_landmarks":{"items":{"type":"string"},"type":"array","title":"Named Landmarks"}},"type":"object","title":"PlannerIntent","description":"Structured intent extracted from a free-text query by Claude Haiku."},"PlannerRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query"},"origin_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Origin Lat"},"origin_lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Origin Lon"},"radius_km":{"type":"number","maximum":200.0,"exclusiveMinimum":0.0,"title":"Radius Km","default":20.0},"departure_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Departure Time"},"travel_mode":{"type":"string","pattern":"^(driving|walking|cycling)$","title":"Travel Mode","default":"driving"},"optimize_for_return":{"type":"boolean","title":"Optimize For Return","default":true},"vibes":{"items":{"type":"string"},"type":"array","title":"Vibes"},"companion":{"type":"string","title":"Companion","default":"solo"},"must_include_places":{"items":{"$ref":"#/components/schemas/MustIncludePlace"},"type":"array","title":"Must Include Places"},"prefer_discovery":{"type":"boolean","title":"Prefer Discovery","default":false},"discovery_weight":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Discovery Weight"},"zone_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Zone Lat"},"zone_lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Zone Lon"},"zone_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone Name"},"previous_plan":{"anyOf":[{"$ref":"#/components/schemas/Plan"},{"type":"null"}]},"previous_plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Previous Plan Id"},"follow_up_query":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Follow Up Query"},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone"},"language":{"type":"string","maxLength":20,"minLength":2,"title":"Language","default":"en"},"campaign_slug":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Campaign Slug"},"anchor_place_id":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Anchor Place Id"}},"type":"object","required":["query"],"title":"PlannerRequest","description":"Request body for POST /planner/query."},"PlannerResponse":{"properties":{"generated_plan_id":{"type":"string","format":"uuid","title":"Generated Plan Id"},"query":{"type":"string","title":"Query"},"intent":{"$ref":"#/components/schemas/PlannerIntent"},"plans":{"items":{"$ref":"#/components/schemas/Plan"},"type":"array","title":"Plans"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"generated_at":{"type":"string","format":"date-time","title":"Generated At"},"plans_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plans Used"},"plans_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plans Limit"}},"type":"object","required":["generated_plan_id","query","intent","plans","weather","generated_at"],"title":"PlannerResponse","description":"Response for POST /planner/query."},"PlanningConflictOut":{"properties":{"type":{"type":"string","title":"Type"},"stop_name":{"type":"string","title":"Stop Name"},"detail":{"type":"string","title":"Detail"},"suggestion":{"type":"string","title":"Suggestion"}},"type":"object","required":["type","stop_name","detail","suggestion"],"title":"PlanningConflictOut"},"PublishResponse":{"properties":{"share_url":{"type":"string","title":"Share Url"}},"type":"object","required":["share_url"],"title":"PublishResponse"},"PushTokenUpdate":{"properties":{"token":{"type":"string","maxLength":255,"minLength":1,"title":"Token"}},"type":"object","required":["token"],"title":"PushTokenUpdate"},"RCEvent":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"type":{"type":"string","title":"Type"},"app_user_id":{"type":"string","title":"App User Id"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"expiration_at_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expiration At Ms"},"environment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Environment"}},"type":"object","required":["type","app_user_id"],"title":"RCEvent"},"RCWebhookPayload":{"properties":{"api_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Version"},"event":{"$ref":"#/components/schemas/RCEvent"}},"type":"object","required":["event"],"title":"RCWebhookPayload"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshRequest"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"ScoutCandidate":{"properties":{"name":{"type":"string","title":"Name"},"one_line_pitch":{"type":"string","title":"One Line Pitch"},"character_tags":{"items":{"type":"string"},"type":"array","title":"Character Tags"},"travel_minutes":{"type":"integer","title":"Travel Minutes"},"saved_places_in_area":{"items":{"type":"string"},"type":"array","title":"Saved Places In Area"},"why_this_fits":{"type":"string","title":"Why This Fits"},"caveats":{"type":"string","title":"Caveats","default":""},"zone_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Zone Lat"},"zone_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Zone Lon"}},"type":"object","required":["name","one_line_pitch","travel_minutes","why_this_fits"],"title":"ScoutCandidate","description":"A candidate day-trip destination area proposed by the scout."},"ScoutRequest":{"properties":{"query":{"type":"string","maxLength":500,"minLength":1,"title":"Query"},"origin_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Origin Lat"},"origin_lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Origin Lon"},"radius_km":{"type":"number","maximum":500.0,"exclusiveMinimum":0.0,"title":"Radius Km","default":200.0},"departure_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Departure Time"}},"type":"object","required":["query"],"title":"ScoutRequest","description":"Request body for POST /planner/scout-destinations."},"ScoutResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/ScoutCandidate"},"type":"array","title":"Candidates"},"reasoning_summary":{"type":"string","title":"Reasoning Summary","default":""},"fewer_candidates_reason":{"type":"string","title":"Fewer Candidates Reason","default":""},"intent":{"$ref":"#/components/schemas/PlannerIntent"}},"type":"object","required":["candidates","intent"],"title":"ScoutResponse","description":"Response for POST /planner/scout-destinations."},"SocialAuthRequest":{"properties":{"provider":{"type":"string","enum":["apple","google"],"title":"Provider"},"id_token":{"type":"string","title":"Id Token"},"display_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Display Name"}},"type":"object","required":["provider","id_token"],"title":"SocialAuthRequest"},"SocialAuthResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"user":{"$ref":"#/components/schemas/UserAuthInfo"},"is_new_user":{"type":"boolean","title":"Is New User"}},"type":"object","required":["access_token","refresh_token","user","is_new_user"],"title":"SocialAuthResponse"},"SocialImportJobOut":{"properties":{"job_id":{"type":"string","format":"uuid","title":"Job Id"},"status":{"type":"string","title":"Status"},"poll_url":{"type":"string","title":"Poll Url"},"social_save_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Social Save Id"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"progress_events":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Progress Events"}},"type":"object","required":["job_id","status","poll_url","created_at"],"title":"SocialImportJobOut"},"SocialImportRequest":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Full URL of the social post to import"},"source":{"type":"string","enum":["instagram","tiktok","youtube","other","ocr"],"title":"Source"},"user_comment":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"User Comment","description":"Optional note the user added before sharing"},"ocr_text":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Ocr Text","description":"Pre-extracted OCR text from the device (source=ocr only)"}},"type":"object","required":["source"],"title":"SocialImportRequest"},"SocialSaveExtractionStatsResponse":{"properties":{"period_days":{"type":"integer","title":"Period Days"},"total":{"type":"integer","title":"Total"},"by_layer":{"items":{"$ref":"#/components/schemas/ExtractionLayerStat"},"type":"array","title":"By Layer"}},"type":"object","required":["period_days","total","by_layer"],"title":"SocialSaveExtractionStatsResponse"},"SocialSaveOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"source_url":{"type":"string","title":"Source Url"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"caption_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption Excerpt"},"extracted_place_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Place Name"},"extracted_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extracted Address"},"place_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Place Id"},"extraction_layer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extraction Layer"},"extraction_attempts":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Extraction Attempts"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","source_url","thumbnail_url","caption_excerpt","extracted_place_name","extracted_address","place_id","created_at"],"title":"SocialSaveOut","description":"Read-only view of a SocialSave row — used in the data-export endpoint."},"SourceType":{"type":"string","enum":["manual","google_import","url_import","screenshot","social_save"],"title":"SourceType"},"SyncStatusResponse":{"properties":{"places_last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Places Last Modified"},"collections_last_modified":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Collections Last Modified"},"profile_last_modified":{"type":"string","format":"date-time","title":"Profile Last Modified"}},"type":"object","required":["profile_last_modified"],"title":"SyncStatusResponse","description":"GET /users/me/sync-status — lightweight staleness check.\n\nMobile calls this on foreground resume to decide which cached responses\nneed refreshing.  All queries are simple MAX(updated_at) aggregates — no\nheavy joins.\n\n``collections_last_modified`` is None until the collections feature ships\na backend implementation."},"TagSuggestionResponse":{"properties":{"suggested":{"items":{"type":"string"},"type":"array","title":"Suggested"},"vocabulary":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object","title":"Vocabulary"}},"type":"object","required":["suggested","vocabulary"],"title":"TagSuggestionResponse"},"TagWithCount":{"properties":{"tag":{"type":"string","title":"Tag"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["tag","count"],"title":"TagWithCount"},"TenantApifyResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"slug":{"type":"string","title":"Slug"},"apify_enabled":{"type":"boolean","title":"Apify Enabled"},"apify_daily_limit":{"type":"integer","title":"Apify Daily Limit"}},"type":"object","required":["tenant_id","slug","apify_enabled","apify_daily_limit"],"title":"TenantApifyResponse"},"TenantBrandingResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"slug":{"type":"string","title":"Slug"},"branding":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Branding"}},"type":"object","required":["tenant_id","slug","branding"],"title":"TenantBrandingResponse"},"TenantUsageResponse":{"properties":{"tenant_id":{"type":"string","title":"Tenant Id"},"slug":{"type":"string","title":"Slug"},"plan_limit_per_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plan Limit Per Month"},"plans_this_month":{"type":"integer","title":"Plans This Month"},"all_time_plans":{"type":"integer","title":"All Time Plans"}},"type":"object","required":["tenant_id","slug","plan_limit_per_month","plans_this_month","all_time_plans"],"title":"TenantUsageResponse"},"TimeWindow":{"properties":{"start":{"type":"string","format":"date-time","title":"Start"},"end":{"type":"string","format":"date-time","title":"End"}},"type":"object","required":["start","end"],"title":"TimeWindow"},"TodayIdea":{"properties":{"label":{"type":"string","title":"Label"},"summary":{"type":"string","title":"Summary"},"mood":{"type":"string","title":"Mood"},"duration_mins":{"type":"integer","title":"Duration Mins"},"stops":{"items":{"$ref":"#/components/schemas/TodayIdeaStop"},"type":"array","title":"Stops"},"plan_snapshot":{"$ref":"#/components/schemas/Plan"}},"type":"object","required":["label","summary","mood","duration_mins","stops","plan_snapshot"],"title":"TodayIdea","description":"A compact plan idea surfaced proactively on the home screen."},"TodayIdeaStop":{"properties":{"name":{"type":"string","title":"Name"},"category":{"type":"string","title":"Category"}},"type":"object","required":["name","category"],"title":"TodayIdeaStop"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["access_token","refresh_token"],"title":"TokenResponse"},"TransitionSegment":{"properties":{"mode":{"type":"string","title":"Mode"},"minutes":{"type":"integer","title":"Minutes"},"distance_km":{"type":"number","title":"Distance Km"},"is_estimated":{"type":"boolean","title":"Is Estimated","default":true}},"type":"object","required":["mode","minutes","distance_km"],"title":"TransitionSegment","description":"Travel segment between two consecutive stops within the zone."},"TransportModeUpdate":{"properties":{"transport_modes":{"items":{"type":"string"},"type":"array","title":"Transport Modes"},"update_radius":{"type":"boolean","title":"Update Radius","default":false}},"type":"object","required":["transport_modes"],"title":"TransportModeUpdate","description":"PATCH /users/me/transport — update transport mode preferences.\n\ntransport_modes:  list of enabled modes (validated by TransportService)\nupdate_radius:    if True, also reset default_radius_km to the mode default\n                  for the primary (first) mode"},"TravelLegOut":{"properties":{"from_stop":{"type":"string","title":"From Stop"},"to_stop":{"type":"string","title":"To Stop"},"distance_km":{"type":"number","title":"Distance Km"},"travel_mins":{"type":"integer","title":"Travel Mins"},"travel_mode":{"type":"string","title":"Travel Mode"}},"type":"object","required":["from_stop","to_stop","distance_km","travel_mins","travel_mode"],"title":"TravelLegOut"},"TripMemoryCreate":{"properties":{"plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Plan Id"},"ai_recap":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Recap"},"mood_tag":{"$ref":"#/components/schemas/MoodTag"},"cover_photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Photo Url"},"stop_ratings":{"additionalProperties":true,"type":"object","title":"Stop Ratings","default":{}},"stop_names":{"items":{"type":"string"},"type":"array","title":"Stop Names","default":[]},"highlight_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Highlight Note"},"is_shared":{"type":"boolean","title":"Is Shared","default":false},"regenerate_recap":{"type":"boolean","title":"Regenerate Recap","default":false}},"type":"object","required":["mood_tag"],"title":"TripMemoryCreate"},"TripMemoryResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Plan Id"},"ai_recap":{"type":"string","title":"Ai Recap"},"mood_tag":{"$ref":"#/components/schemas/MoodTag"},"cover_photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Photo Url"},"stop_ratings":{"additionalProperties":true,"type":"object","title":"Stop Ratings"},"stop_names":{"items":{"type":"string"},"type":"array","title":"Stop Names","default":[]},"highlight_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Highlight Note"},"is_shared":{"type":"boolean","title":"Is Shared"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","user_id","plan_id","ai_recap","mood_tag","cover_photo_url","stop_ratings","highlight_note","is_shared","created_at","updated_at"],"title":"TripMemoryResponse"},"TripMemoryUpdate":{"properties":{"ai_recap":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Recap"},"mood_tag":{"anyOf":[{"$ref":"#/components/schemas/MoodTag"},{"type":"null"}]},"cover_photo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cover Photo Url"},"stop_ratings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Stop Ratings"},"highlight_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Highlight Note"},"is_shared":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Shared"}},"type":"object","title":"TripMemoryUpdate"},"UnfurlRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url","description":"Any URL to extract place data from"},"caption_hint":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Caption Hint","description":"Optional plain-text caption pasted by the user (e.g. from Instagram). Used as primary AI input when the server cannot fetch the page directly."}},"type":"object","required":["url"],"title":"UnfurlRequest"},"UnfurlResult":{"properties":{"source_url":{"type":"string","title":"Source Url"},"resolved":{"type":"boolean","title":"Resolved"},"paywalled":{"type":"boolean","title":"Paywalled","default":false},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"caption_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Caption Excerpt"},"google_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Place Id"},"saved_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Saved Place Id"},"extracted_places":{"items":{"$ref":"#/components/schemas/PlaceData"},"type":"array","title":"Extracted Places"}},"type":"object","required":["source_url","resolved"],"title":"UnfurlResult","description":"Structured result from the link unfurl pipeline.\n\nIncludes enough data for the mobile app to show a confirmation card\nbefore the user commits to saving the place.\n\n`resolved` is True when a Google Places ID was found and the full\nPlaces API record has been loaded; False means we only have whatever\nwas in the page metadata or AI extraction.\n\n`paywalled` is True when the pipeline detected a paywall / subscriber\ngate and could not extract meaningful place data.\n\n`extracted_places` is populated when the AI step found more than one\ndistinct place in the page content, enabling the multi-place checklist\nUI. Empty list otherwise."},"UpdateTenantApifyRequest":{"properties":{"apify_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Apify Enabled"},"apify_daily_limit":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Apify Daily Limit"}},"type":"object","title":"UpdateTenantApifyRequest"},"UpdateTenantBrandingRequest":{"properties":{"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url"},"accent_color":{"anyOf":[{"type":"string","pattern":"^(#[0-9A-Fa-f]{6})?$"},{"type":"null"}],"title":"Accent Color"}},"type":"object","title":"UpdateTenantBrandingRequest"},"UpdateTenantLimitRequest":{"properties":{"plan_limit_per_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Plan Limit Per Month"}},"type":"object","required":["plan_limit_per_month"],"title":"UpdateTenantLimitRequest"},"UserAuthInfo":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"onboarding_completed":{"type":"boolean","title":"Onboarding Completed"},"auth_provider":{"type":"string","title":"Auth Provider"}},"type":"object","required":["id","email","display_name","avatar_url","onboarding_completed","auth_provider"],"title":"UserAuthInfo","description":"Minimal user representation returned inside SocialAuthResponse."},"UserDataExport":{"properties":{"exported_at":{"type":"string","format":"date-time","title":"Exported At"},"profile":{"$ref":"#/components/schemas/UserOut"},"places":{"items":{"$ref":"#/components/schemas/PlaceOut"},"type":"array","title":"Places"},"visits":{"items":{"$ref":"#/components/schemas/VisitOut"},"type":"array","title":"Visits"},"feedback":{"items":{"$ref":"#/components/schemas/FeedbackOut"},"type":"array","title":"Feedback"},"social_saves":{"items":{"$ref":"#/components/schemas/SocialSaveOut"},"type":"array","title":"Social Saves"}},"type":"object","required":["exported_at","profile","places","visits","feedback","social_saves"],"title":"UserDataExport","description":"Full data export — satisfies UK GDPR Art. 15 (SAR) and Art. 20 (portability).\nReturned by GET /users/me/export."},"UserOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","format":"email","title":"Email"},"is_active":{"type":"boolean","title":"Is Active"},"is_verified":{"type":"boolean","title":"Is Verified"},"home_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Lat"},"home_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Lon"},"home_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Label"},"travel_radius_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Travel Radius Km"},"location_history_opt_in":{"type":"boolean","title":"Location History Opt In"},"children":{"items":{"$ref":"#/components/schemas/ChildOut"},"type":"array","title":"Children","default":[]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"auth_provider":{"type":"string","title":"Auth Provider","default":"email"},"onboarding_completed":{"type":"boolean","title":"Onboarding Completed","default":false}},"type":"object","required":["id","email","is_active","is_verified","home_lat","home_lon","home_label","travel_radius_km","location_history_opt_in","created_at"],"title":"UserOut"},"UserResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","format":"email","title":"Email"},"is_active":{"type":"boolean","title":"Is Active"},"is_verified":{"type":"boolean","title":"Is Verified"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"auth_provider":{"type":"string","title":"Auth Provider","default":"email"},"onboarding_completed":{"type":"boolean","title":"Onboarding Completed","default":false},"home_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Lat"},"home_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Lng"},"home_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Address"},"home_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Place Id"},"default_radius_km":{"type":"number","title":"Default Radius Km","default":50.0},"radius_unit":{"type":"string","title":"Radius Unit","default":"km"},"home_lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Home Lon"},"home_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Label"},"travel_radius_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Travel Radius Km"},"transport_modes":{"items":{"type":"string"},"type":"array","title":"Transport Modes"},"primary_transport_mode":{"type":"string","title":"Primary Transport Mode","default":"driving"},"location_history_opt_in":{"type":"boolean","title":"Location History Opt In","default":false},"children":{"items":{"$ref":"#/components/schemas/ChildOut"},"type":"array","title":"Children","default":[]},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","email","is_active","is_verified","created_at"],"title":"UserResponse","description":"Full user profile response.\n\nExposes both the new field names introduced in the home-settings feature\n(home_lng, home_address, default_radius_km, radius_unit) AND the legacy\nnames (home_lon, home_label, travel_radius_km) so existing clients and\ntests are not broken."},"UserUpdateLocation":{"properties":{"last_location_lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Last Location Lat"},"last_location_lng":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Last Location Lng"}},"type":"object","required":["last_location_lat","last_location_lng"],"title":"UserUpdateLocation","description":"PATCH /users/me/location — silent background location update.\nReturns 204 No Content (fire-and-forget from mobile)."},"UserUpdateOnboarding":{"properties":{"home_lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Home Lat"},"home_lng":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Home Lng"},"home_address":{"type":"string","maxLength":255,"title":"Home Address"},"home_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Place Id"},"default_radius_km":{"type":"number","maximum":300.0,"minimum":2.0,"title":"Default Radius Km"},"radius_unit":{"type":"string","enum":["km","miles"],"title":"Radius Unit","default":"km"},"transport_modes":{"items":{"type":"string"},"type":"array","title":"Transport Modes"},"primary_transport_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Transport Mode"}},"type":"object","required":["home_lat","home_lng","home_address","default_radius_km"],"title":"UserUpdateOnboarding","description":"POST /users/me/complete-onboarding — all location/radius fields required,\nsets onboarding_completed = True in one atomic call."},"UserUpdateSettings":{"properties":{"home_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Home Lat"},"home_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Home Lng"},"home_address":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Home Address"},"home_place_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Home Place Id"},"default_radius_km":{"anyOf":[{"type":"number","maximum":300.0,"minimum":2.0},{"type":"null"}],"title":"Default Radius Km"},"radius_unit":{"anyOf":[{"type":"string","enum":["km","miles"]},{"type":"null"}],"title":"Radius Unit"},"home_lon":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Home Lon"},"home_label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Home Label"},"travel_radius_km":{"anyOf":[{"type":"number","maximum":300.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Travel Radius Km"},"onboarding_completed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Onboarding Completed"},"transport_modes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Transport Modes"},"primary_transport_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Transport Mode"}},"type":"object","title":"UserUpdateSettings","description":"PATCH /users/me — partial update of home/radius settings.\n\nAccepts both new field names (home_lng, home_address, default_radius_km,\nradius_unit) and legacy names (home_lon, home_label, travel_radius_km)\nso existing mobile clients that haven't updated yet continue to work.\nThe service layer coalesces duplicate fields."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VisitCreate":{"properties":{"place_id":{"type":"string","format":"uuid","title":"Place Id"},"visited_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Visited At"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"duration_mins":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Mins"},"companions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companions"},"plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Plan Id"}},"type":"object","required":["place_id"],"title":"VisitCreate","description":"Richer visit creation request (used by VisitHistoryScreen log sheet)."},"VisitOut":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"place_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Place Id"},"visited_at":{"type":"string","format":"date-time","title":"Visited At"},"source":{"type":"string","title":"Source"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","place_id","visited_at","source","created_at"],"title":"VisitOut","description":"Minimal visit shape (backward-compatible)."},"VisitPlaceSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"place_type":{"anyOf":[{"$ref":"#/components/schemas/PlaceType"},{"type":"null"}]},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lat"},"lon":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lon"}},"type":"object","required":["id","name","place_type","address","city","lat","lon"],"title":"VisitPlaceSummary","description":"Compact place info embedded in visit responses."},"VisitResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"place_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Place Id"},"visited_at":{"type":"string","format":"date-time","title":"Visited At"},"source":{"type":"string","title":"Source"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"duration_mins":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Mins"},"companions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Companions"},"plan_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Plan Id"},"weather":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weather"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"place":{"anyOf":[{"$ref":"#/components/schemas/VisitPlaceSummary"},{"type":"null"}]}},"type":"object","required":["id","user_id","place_id","visited_at","source","rating","notes","duration_mins","companions","plan_id","weather","created_at","place"],"title":"VisitResponse","description":"Full visit shape including nested place summary."},"VisitStats":{"properties":{"total_visits":{"type":"integer","title":"Total Visits"},"avg_rating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Rating"},"last_visited_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Visited At"}},"type":"object","required":["total_visits","avg_rating","last_visited_at"],"title":"VisitStats"}},"securitySchemes":{"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"},"HTTPBearer":{"type":"http","scheme":"bearer"}}}}