{"openapi":"3.1.0","info":{"title":"Geoa","version":"0.5.0"},"paths":{"/api/audit-preview":{"post":{"summary":"Audit Preview","description":"Gratis GEO-test — kører ægte Unified GEO Score (15 dimensioner, 0 kr drift).","operationId":"audit_preview_api_audit_preview_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/llms-txt-generate":{"post":{"summary":"Llms Txt Generate","description":"Generate llms.txt for a domain — public free tool.\n\nTo sprog i spil, bevidst: UI'et følger RUTE-locale (det læser besøgende),\nmens den genererede FIL følger det analyserede sites eget sprog (den ender\npå kundens domæne). Rute-locale sendes derfor kun med som fil-fallback.\nSe sprog-afsnittet i llms_txt_generator.py.","operationId":"llms_txt_generate_api_llms_txt_generate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/geo-prompts-generate":{"post":{"summary":"Geo Prompts Generate","description":"Gratis GEO Prompt Generator — de prompts kunder stiller AI om et emne.\n\nKalder prompt_generator.generate_for_topic (ét billigt LLM-kald, fail-open til\nskabeloner). 6t cache + per-IP dagligt loft. Inline HTML (htmx). Intet login.","operationId":"geo_prompts_generate_api_geo_prompts_generate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/content-brief-generate":{"post":{"summary":"Content Brief Generate","description":"Gratis Content Brief Generator — databaseret disposition ud fra SERP-analyse.\n\nKalder content_brief.generate_content_brief (Brave Search, gratis tier). 6t cache\npr. (topic, industry). Returnerer inline HTML (htmx innerHTML). Intet login.","operationId":"content_brief_generate_api_content_brief_generate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/nordic-citation-check":{"post":{"summary":"Nordic Citation Check Tool","description":"Gratis Nordic Citation Checker — tjekker virksomhedens tilstedeværelse i de\nnordiske erhvervskataloger (krak/dineo/gulesider/hitta/eniro/Trustpilot).\n\nKalder nordic_citations.check_nordic_citations (scraper direkte, ingen betalt API,\nfail-open pr. katalog). 6t cache. Returnerer inline HTML (htmx innerHTML). Intet login.","operationId":"nordic_citation_check_tool_api_nordic_citation_check_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/mention-check":{"post":{"summary":"Mention Check","description":"Gratis AI Mention Checker — analyserer et indsat AI-svar for brand-omtale.\n\nKalder mention_analyzer.analyze_ai_response: GRATIS regex-detektion + en billig\nsentiment-LLM KUN hvis brandet faktisk findes i svaret. Returnerer inline HTML\n(htmx innerHTML). Ingen DB-skrivning, intet login.","operationId":"mention_check_api_mention_check_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/signup-mention-check":{"post":{"summary":"Signup Mention Check","description":"Persistér signup-flowets brand-scan som kundens FØRSTE måling.\n\nGEO-tool-eksekvering #7 (2026-08-24): signup-trin 1 på platform.geoa.app\ner den gratis mention-check (indsæt AI-svar). Når kontoen ER oprettet,\nkalder frontenden dette endpoint, så målingen følger med ind i\nonboardingen (ai_scan_results, source='signup_check') i stedet for at dø\npå en værktøjsside. Analysen GEN-KØRES server-side — klientens udlægning\naf resultatet er aldrig sandhedskilden.\n\nAuth: HERMES eller ejerens bruger-JWT (_authorize_dashboard_write —\nledger-ført i test_state_mutating_routes_guard).\nBody: {ai_response, brand_name, prompt_text, platform?}","operationId":"signup_mention_check_api_customers__customer_id__signup_mention_check_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/readability-check":{"post":{"summary":"Readability Check","description":"Gratis AI Readability Check — hvad ser en AI-crawler uden JavaScript?\n\nDeterministisk kerne (rå HTML + robots.txt + llms.txt + JSON-LD +\nbuilder-fingerprint); Jina bruges fail-open som \"menneske-visning\" på\ntynde sider så kontrasten kan sættes i tal. Returnerer inline HTML\n(htmx innerHTML). Ingen DB-skrivning, intet login. Kost-guards\n(domæne-cache + per-IP/globalt dagsloft) ligger i readability_check.py.","operationId":"readability_check_api_readability_check_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/exit-capture":{"post":{"summary":"Exit Capture","description":"Capture email from post-audit — creates customer and starts real onboarding.","operationId":"exit_capture_api_exit_capture_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/mcp":{"get":{"summary":"Mcp Get","operationId":"mcp_get_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Mcp Endpoint","operationId":"mcp_endpoint_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/embed/badge/{public_id}.svg":{"get":{"tags":["badge"],"summary":"Badge Svg","description":"Offentligt embed — ligger på KUNDENS site, så: aldrig 500, altid billede\n(404 kun ved ukendt/ugyldigt id, så snippet-tyveri ikke kan probe os).","operationId":"badge_svg_embed_badge__public_id__svg_get","parameters":[{"name":"public_id","in":"path","required":true,"schema":{"type":"string","title":"Public Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/badge/snippet":{"get":{"tags":["badge"],"summary":"Badge Snippet","operationId":"badge_snippet_api_badge_snippet_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/badge/verify":{"post":{"tags":["badge"],"summary":"Badge Verify","operationId":"badge_verify_api_badge_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["auth"],"summary":"Auth Login","description":"Email/password login via Supabase Auth.","operationId":"auth_login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/signup":{"post":{"tags":["auth"],"summary":"Auth Signup","description":"Email/password signup via Supabase Auth.","operationId":"auth_signup_api_auth_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/magic-link":{"post":{"tags":["auth"],"summary":"Auth Magic Link","description":"Send magic link email via Supabase Auth.","operationId":"auth_magic_link_api_auth_magic_link_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/callback":{"get":{"tags":["auth"],"summary":"Auth Callback","description":"Handle Supabase Auth callback (OAuth, magic link redirect).","operationId":"auth_callback_api_auth_callback_get","parameters":[{"name":"access_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token"}},{"name":"refresh_token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["auth"],"summary":"Auth Logout","description":"Logout — clear session cookie.","operationId":"auth_logout_api_auth_logout_post","parameters":[{"name":"geoa_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geoa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"tags":["auth"],"summary":"Auth Me","description":"Get current user info + linked customers.","operationId":"auth_me_api_auth_me_get","parameters":[{"name":"geoa_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geoa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/check-email":{"post":{"tags":["auth"],"summary":"Check Email Exists","description":"Check if an email is already registered.","operationId":"check_email_exists_api_auth_check_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicLinkRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/forgot-password":{"post":{"tags":["auth"],"summary":"Auth Forgot Password","description":"Send password reset email via Supabase Auth.\n\nSupabase sends a recovery email with a link to:\nhttps://geoa.app/reset-password?type=recovery&access_token=...","operationId":"auth_forgot_password_api_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/reset-password":{"post":{"tags":["auth"],"summary":"Auth Reset Password","description":"Complete password reset with recovery token + new password.\n\nCalled from the /reset-password page after user enters new password.\nUses the recovery access_token from the Supabase redirect URL.\n\nOn success, logs the user in automatically (sets session cookie).","operationId":"auth_reset_password_api_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/change-password":{"post":{"tags":["auth"],"summary":"Auth Change Password","description":"Change password for authenticated user.\n\nRequires current password for verification.\nUpdates Supabase Auth password, then re-issues session cookie.","operationId":"auth_change_password_api_auth_change_password_post","parameters":[{"name":"geoa_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geoa Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/delete-account":{"post":{"tags":["auth"],"summary":"Auth Delete Account","description":"Delete the authenticated user's account — SAMME kaskade som\n/api/me/account (rettet 2026-08-28).\n\nDen gamle krop slettede KUN auth-brugeren og påstod at \"RLS will\ncascade-delete linked records\" — det gør RLS ikke: customers-rækken og\nalt under den blev stående forældreløst, stik imod DPA-løftet om fuld\nsletning inden 30 dage. Nu genbruges me_profile-kaskaden, inkl. de to\n409-værn (aktivt abonnement / andre teammedlemmer) — et værn den her\nrute aldrig havde, så den var også en smutvej uden om dem.","operationId":"auth_delete_account_api_auth_delete_account_post","parameters":[{"name":"geoa_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Geoa Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/nordic/address":{"get":{"tags":["nordic-data"],"summary":"Nordic Address","description":"Address autocomplete for the onboarding form (DAWA/Kartverket/...).","operationId":"nordic_address_api_nordic_address_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":5,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/nordic/company":{"get":{"tags":["nordic-data"],"summary":"Nordic Company","description":"Confirm-your-company preview for onboarding. Read-only, no DB write.\n\nIf *q* looks like a CVR/orgnr number (8 digits DK, 9 NO, 10 SE, 8 FI)\nit is routed directly to the id-lookup endpoint so the user gets the\nexact company rather than a fuzzy name-search result.","operationId":"nordic_company_api_nordic_company_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/nordic/company/{country}/{company_id}":{"get":{"tags":["nordic-data"],"summary":"Nordic Company By Id","description":"Precise CVR/orgnr id-lookup, normalised for schema generation\n(LocalBusiness/Organization in the WordPress plugin & Shopify app).\n\nUnlike /company (name search, ambiguous) this resolves an exact national id,\nso callers can drop the confirm-your-company gate. DK's empty\nindustry.description is backfilled from the name-search result (cvrapi.dk),\nand contact fields are omitted entirely when marketingProtection is set (GDPR).\nRead-only, no DB write — mirrors the open access model of the sibling routes.","operationId":"nordic_company_by_id_api_nordic_company__country___company_id__get","parameters":[{"name":"country","in":"path","required":true,"schema":{"type":"string","title":"Country"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/onboarding/peek":{"get":{"tags":["onboarding"],"summary":"Onboarding Peek","description":"Identitet + ydelse + arketype læst fra sitet og registret — FØR oprettelse.\n\nRen læsning: ingen DB-skrivning. Én sidehentning + ét registeropslag pr.\ndomæne pr. 15 min. Svaret bærer `basis` pr. felt og `conflicts`, så\nwizarden kan vise kilden og lade kunden vælge ved uenighed.","operationId":"onboarding_peek_api_onboarding_peek_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","minLength":4,"title":"Domain"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"","title":"Country"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/start-scan":{"post":{"summary":"Start Scan","description":"Offentlig med vilje — det er tragtens første flade (som /api/audit-preview).\nVærnet er IP-dagsloft + domæne-cache + globalt loft, ikke login.","operationId":"start_scan_api_start_scan_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/start-scan/{scan_id}/rendered":{"post":{"summary":"Start Scan Rendered","description":"B8: klienten melder, at resultatet STOD på skærmen. Måler tiden fra\ndomænet blev tastet til resultatet var renderet (rapportens mål 2:\n< 90 sek) — ikke til stage='done'. Én gang pr. scan; offentlig som\nresten af /start, og den skriver kun én telemetri-række uden persondata.","operationId":"start_scan_rendered_api_start_scan__scan_id__rendered_post","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/start-scan/{scan_id}":{"get":{"summary":"Get Scan","operationId":"get_scan_api_start_scan__scan_id__get","parameters":[{"name":"scan_id","in":"path","required":true,"schema":{"type":"string","title":"Scan Id"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","default":"","title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/analytics/track":{"post":{"tags":["analytics"],"summary":"Track Event","description":"Track a user journey event.\n\nEvents we track:\n- page_view: User viewed a page\n- onboarding_step: User completed an onboarding step\n- drop_off: User dropped off at a specific point\n- aha_moment: User reached the \"Aha!\" moment (first GEO score, first content download)\n- feature_used: User used a specific feature","operationId":"track_event_api_analytics_track_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/analytics/journey-overview":{"get":{"tags":["analytics"],"summary":"Get Journey Overview","description":"Get overview of customer journey metrics.\n\nReturns:\n- Onboarding completion rate\n- Drop-off points\n- Average TTV (Time to Value)\n- Aha! moment conversion rate","operationId":"get_journey_overview_api_analytics_journey_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/analytics/cohort-retention":{"get":{"tags":["analytics"],"summary":"Get Cohort Retention","description":"Get cohort retention analysis.\n\nShows how different cohorts (signup groups) retain over time.","operationId":"get_cohort_retention_api_analytics_cohort_retention_get","parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"weekly","title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/capabilities":{"get":{"tags":["review-hub"],"summary":"Get Capabilities","operationId":"get_capabilities_api_capabilities_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tenant/me":{"get":{"tags":["review-hub"],"summary":"Tenant Me","operationId":"tenant_me_api_tenant_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/tenant/billing-ref":{"get":{"tags":["review-hub"],"summary":"Tenant Billing Ref","description":"Kontoens eget Stripe-customer-id — til at åbne Stripes kundeportal.\n\nEgen rute frem for et felt i /api/tenant/me: portal-linket laves ÉN gang,\nnår kunden trykker, mens /api/tenant/me hentes ved hvert sideskift. Id'et\nhar intet at gøre på hver eneste sideindlæsning.\n\nIkke en hemmelighed (et `cus_…` kan intet uden Stripe-nøglen, og den bor i\nplatformen), men det er kundens eget — derfor kræver ruten hendes token og\nsvarer kun for hendes egen tenant.","operationId":"tenant_billing_ref_api_tenant_billing_ref_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/usage":{"get":{"tags":["review-hub"],"summary":"Usage","description":"Forbrug mod månedsloftet — talt i invitationstabellen, samme regel som\nkvote-vagten i POST /api/invitations bruger (review_invitations.usage_for).","operationId":"usage_api_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/plans":{"get":{"tags":["review-hub"],"summary":"Plans","operationId":"plans_api_plans_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/locations":{"get":{"tags":["review-hub"],"summary":"List Locations","operationId":"list_locations_api_locations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["review-hub"],"summary":"Create Location","operationId":"create_location_api_locations_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/locations/{location_id}":{"patch":{"tags":["review-hub"],"summary":"Update Location","operationId":"update_location_api_locations__location_id__patch","parameters":[{"name":"location_id","in":"path","required":true,"schema":{"type":"string","title":"Location Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/locations/place-search":{"get":{"tags":["review-hub"],"summary":"Place Search","description":"Find en virksomhed ud fra navn og by.\n\nÆRLIG AFGRÆNSNING: det her er ét opslag, ikke en kandidatliste. Vores\nGBP-opslag returnerer det bedste match, ikke de fem nærmeste, så svaret er\nenten én kandidat eller ingen. Onboardingen skal derfor spørge \"er det her\ndig?\" og ikke \"vælg din virksomhed\" — og en tom liste er et ærligt svar,\nikke en fejl.","operationId":"place_search_api_locations_place_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Q"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/locations/{location_id}/reputation":{"get":{"tags":["review-hub"],"summary":"Location Reputation","description":"Omdømme for én lokation, pr. kilde — aldrig blandet.\n\nBemærk at stien IKKE er /api/reputation/{id} som i kontraktudkastet: den\nville kollidere med det eksisterende /api/reputation/{customer_id}, der har\nlige så mange segmenter. Fladen skal kalde den her.","operationId":"location_reputation_api_locations__location_id__reputation_get","parameters":[{"name":"location_id","in":"path","required":true,"schema":{"type":"string","title":"Location Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/measurements":{"get":{"tags":["review-hub"],"summary":"Measurements","description":"Vores egne målepunkter over tid — den lovlige historik.\n\nDet her er IKKE gemt Google-indhold. Det er det tal, vi selv målte, den dag\nvi målte det, og det er derfor det er tilladt at tegne en kurve af.","operationId":"measurements_api_measurements_get","parameters":[{"name":"location_id","in":"query","required":true,"schema":{"type":"string","title":"Location Id"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"","title":"Source"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":730,"default":180,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/google/auth-url":{"get":{"tags":["review-hub"],"summary":"Google Auth Url","operationId":"google_auth_url_api_google_auth_url_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/google/status":{"get":{"tags":["review-hub"],"summary":"Google Status","operationId":"google_status_api_google_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/google":{"delete":{"tags":["review-hub"],"summary":"Google Disconnect","operationId":"google_disconnect_api_google_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/reviews":{"get":{"tags":["review-hub"],"summary":"Reviews","description":"Alle koblede lokationers Google-anmeldelser, ubesvarede først.","operationId":"reviews_api_reviews_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/reviews/{rid}/suggestion":{"get":{"tags":["review-hub"],"summary":"Suggestion","operationId":"suggestion_api_reviews__rid__suggestion_get","parameters":[{"name":"rid","in":"path","required":true,"schema":{"type":"string","title":"Rid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reviews/{rid}/reply":{"post":{"tags":["review-hub"],"summary":"Reply","description":"Udgiv ÉT svar, kunden har godkendt. Genlæses hos Google bagefter.","operationId":"reply_api_reviews__rid__reply_post","parameters":[{"name":"rid","in":"path","required":true,"schema":{"type":"string","title":"Rid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/review_hub_google__ReplyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/preview":{"post":{"tags":["review-hub-invitations"],"summary":"Preview Invitation","description":"Præcis det, modtageren får — med oplysningerne, der ikke kan slås fra.","operationId":"preview_invitation_api_invitations_preview_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations":{"post":{"tags":["review-hub-invitations"],"summary":"Create Invitation","description":"Manuel enkelt-invitation (kravspec §4 trigger 3). Sendes med det samme,\nmedmindre rolige timer skubber den til morgenen.","operationId":"create_invitation_api_invitations_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["review-hub-invitations"],"summary":"List Invitations","operationId":"list_invitations_api_invitations_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/invitations/{invitation_id}/cancel":{"post":{"tags":["review-hub-invitations"],"summary":"Cancel Invitation","operationId":"cancel_invitation_api_invitations__invitation_id__cancel_post","parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/suppression":{"get":{"tags":["review-hub-invitations"],"summary":"Get Suppression","description":"KUN antal og tidsstempler — aldrig identiteter (kravspec §3 krav 4).","operationId":"get_suppression_api_suppression_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["review-hub-invitations"],"summary":"Add Suppression","description":"Kunden ringer og beder om ikke at få flere beskeder. Råværdien gemmes ikke.","operationId":"add_suppression_api_suppression_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/consents":{"get":{"tags":["review-hub-invitations"],"summary":"Consent Summary","description":"Antal kunder med gældende ja/nej og kilderne — aldrig identiteter.","operationId":"consent_summary_api_consents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["review-hub-invitations"],"summary":"Register Consents","description":"Registrér samtykke (eller et nej) for én eller flere kunder.\n\nRåværdierne gemmes ikke — kun hash, samme nøgle som spærrelisten. Svaret\nsiger hvor mange der blev registreret og hvilke linjer der ikke kunne læses,\nmed linjenummer, så importen kan vise dem. Aldrig kontaktværdierne.","operationId":"register_consents_api_consents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentBatchIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/consents/lookup":{"post":{"tags":["review-hub-invitations"],"summary":"Lookup Consent","description":"Har kunden allerede sagt ja? POST, så nummeret ikke står i en URL/log.","operationId":"lookup_consent_api_consents_lookup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentLookupIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/r/i/{token}":{"get":{"tags":["review-hub-invitations"],"summary":"Open Review Link","operationId":"open_review_link_r_i__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/r/o/{token}":{"get":{"tags":["review-hub-invitations"],"summary":"Optout Page","operationId":"optout_page_r_o__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["review-hub-invitations"],"summary":"Optout Confirm","description":"Også RFC 8058-målet for List-Unsubscribe-Post (én-kliks framelding).","operationId":"optout_confirm_r_o__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/gatewayapi":{"post":{"tags":["review-hub-invitations"],"summary":"Gatewayapi Webhook","operationId":"gatewayapi_webhook_api_webhooks_gatewayapi_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/webhooks/brevo":{"post":{"tags":["review-hub-invitations"],"summary":"Brevo Webhook","operationId":"brevo_webhook_api_webhooks_brevo_post","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/sync":{"post":{"tags":["review-hub-billing"],"summary":"Billing Sync","description":"Stripe → platformens serverfunktion → HER. Aldrig fra en browser.","operationId":"billing_sync_api_billing_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingSync"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/register":{"post":{"tags":["apps"],"summary":"Register App Installation","description":"Mint a per-installation key (server-to-server; HERMES-guarded).\n\nBody: {product: 'shopify'|'wordpress', external_id: <shop domain|site url>,\n       customer_id?: <uuid>, domain?: <customer domain>}\nExactly one of customer_id / domain is required to bind the install to a\ntenant. Rotates: any prior active key for the same product+external_id is\nrevoked. Returns the plaintext api_key ONCE.","operationId":"register_app_installation_api_apps_register_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/pairing":{"post":{"tags":["apps"],"summary":"Create Pairing Code","description":"Mint a short-lived, single-use pairing code (server-to-server; HERMES).\n\nThe first-party trust step: our frontend/backend binds a customer to a code,\nwhich the merchant then pastes into their Shopify/WordPress app to self-serve\nits per-install key via /claim — without ever holding the shared HERMES secret.\n\nBody: {customer_id?|domain?, product?('shopify'|'wordpress'|omit=any),\n       ttl_minutes?(5..1440, default 60)}\nReturns the plaintext pairing_code ONCE.","operationId":"create_pairing_code_api_apps_pairing_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/claim":{"post":{"tags":["apps"],"summary":"Claim Pairing Code","description":"Redeem a pairing code for a per-installation key (NO shared secret).\n\nThe self-serve step the distributed client calls: the Shopify app / WordPress\nplugin sends the pairing code plus its own identity (external_id) and receives\nits X-Client-Key once. The code is single-use and short-lived.\n\nBody: {code, product('shopify'|'wordpress'), external_id(shop domain|site url)}","operationId":"claim_pairing_code_api_apps_claim_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/revoke":{"post":{"tags":["apps"],"summary":"Revoke App Installation","description":"Revoke an installation key (HERMES-guarded).\n\nBody: {installation_id: <uuid>} OR {product, external_id}.","operationId":"revoke_app_installation_api_apps_revoke_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/list":{"get":{"tags":["apps"],"summary":"List App Installations","description":"List installations (HERMES-guarded). Never returns key hashes.\n\nOptional filters: ?customer_id= or ?domain=.","operationId":"list_app_installations_api_apps_list_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/me":{"get":{"tags":["apps"],"summary":"Whoami","description":"Client whoami — the plugin verifies its key and discovers its customer.\n\nGuarded by X-Client-Key (the per-install key), NOT the shared HERMES secret.","operationId":"whoami_api_apps_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/entitlements":{"get":{"tags":["apps"],"summary":"App Entitlements","description":"Client entitlement — a distributed client (WordPress/Shopify/PrestaShop)\ndiscovers its OWN plan/status from its X-Client-Key, so it can gate upgrade\nprompts without a user JWT (/api/me/entitlements needs a Supabase JWT the\nclient doesn't have).\n\nAdditive, read-only, guarded by X-Client-Key. Returns only this tenant's\ngeo plan/status + a derived is_paid — never the HERMES key or cross-tenant\ndata. Fail-open: on any lookup error it reports the free (\"none\") plan.\n\nIncludes a `badge` object (see below) so a distributed client can render the\nbadge program's embed. /api/badge/snippet is bruger-JWT-guarded and a plugin\nhas no JWT — this is the X-Client-Key path to the SAME public_id, never a\nsecond auth vej into the JWT routes.","operationId":"app_entitlements_api_apps_entitlements_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/plan-sync":{"post":{"tags":["apps"],"summary":"App Plan Sync","description":"Wix (marketplace-faktureret) melder sin native billing-plan ind via sin\nEGEN X-Client-Key — ALDRIG HERMES (distribuerede klienter må aldrig få\nHERMES-nøglen, jf. CLAUDE.md). Mirror af Shopify-kanalen\n(POST /api/customers/{id}/plan-sync, HERMES) men for X-Client-Key-klienter,\nså connectoren beviser sin egen install-identitet i stedet for at holde en\ndelt hemmelighed.\n\nBody: {packageName: \"<wix vendorProductId>\", status?: active|trialing|\ncanceled|past_due}. Alias for packageName: package_name / vendorProductId.\npackageName mappes → geo_plan via plan_sync.WIX_PLAN_MAP (rigtige Wix Dev\nCenter-IDs, udfyldes downstream) eller identity-fallback (packageName ER\nallerede en geo_plan slug), og spejles ind på installens EGEN customer via\nplan_sync.apply_channel_plan (samme kolonner som Stripe/Shopify-syncen).\n\nFail-open: ukendt/manglende packageName, ikke-Wix produkt, manglende\ncustomer eller sync-fejl → 200 {status:\"ignored\"} UDEN at røre planen.\nAdditivt: Shopify-/Stripe-syncerne er uændrede; kun Wix-installens egen\ncustomer-række berøres.","operationId":"app_plan_sync_api_apps_plan_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/questions":{"get":{"tags":["apps"],"summary":"App List Questions","description":"List this install's tracked questions + usage against the plan limit.\n\nGuarded by X-Client-Key. Returns {questions, used, limit}. Fail-open read\n(an infra hiccup yields an empty set rather than erroring the plugin UI).","operationId":"app_list_questions_api_apps_questions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["apps"],"summary":"App Add Question","description":"Add tracked question(s) to THIS install's customer, limit-enforced.\n\nBody: {\"question\": \"...\"} for one, or BULK (mass-paste):\n{\"questions\": [\"...\", \"...\"]} — a multi-line \"question\" string is also\nsplit on newlines. On the plan ceiling returns HTTP 402 with\n{\"error\":\"question_limit_reached\",\"limit\",\"used\",\"message\"} — the money-layer\nupgrade trigger. On success 200 {\"status\":\"added\",\"used\",\"limit\"} (bulk adds\nadded/skipped counts).","operationId":"app_add_question_api_apps_questions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["apps"],"summary":"App Remove Question","description":"Remove a tracked question from THIS install's customer.\n\nBody {\"question\":\"...\"} or ?question=. 200 on success; 404 if not tracked.","operationId":"app_remove_question_api_apps_questions_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/brands":{"get":{"tags":["apps"],"summary":"App List Brands","description":"Denne installations sporede navne + forbrug mod plan-loftet.\n\nGuardet af X-Client-Key. Returnerer {brands, used, limit}. Fail-open\nlæsning (en infra-hikke giver et tomt sæt frem for at vælte plugin-UI'et).","operationId":"app_list_brands_api_apps_brands_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["apps"],"summary":"App Upsert Brand","description":"Tilføj/opdatér ÉT sporet navn på denne installations kunde.\n\nBody: {\"name\": \"EntityWatch\", \"aliases\": [\"Entity Watch\"], \"primary\": true,\n       \"replace_primary\": false}\n`aliases` er stavevarianter af SAMME navn og tæller ikke med i loftet.\n402 ved plan-loftet med {\"error\":\"brand_limit_reached\", ...} —\nupgrade-triggeren, præcis som /questions.\n\n`replace_primary: true` = OMDØB det primære navn frem for at tilføje et.\nEnkeltnavns-klienter (WP-plugin'et) sender det: at rette sit ene navn er\nikke at spore navn nr. 2, så det må aldrig ramme loftet.\n\n`kind` (2026-08-31) = kundens svar på \"er det her navnet på SELSKABET\neller på et produkt selskabet driver\". Pluginet spørger om det i\nonboardingen og har altid bygget sit eget schema efter svaret — men\nbeholdt svaret lokalt, så fix-produceren byggede én node og satte\nproduktnavnet i `Organization.name` (checkbiz.eu, G-040). Udeladt rører\nikke en gemt værdi.\n\n`source: \"derived\"` (valgfri, 2026-08-30) = \"det her er ikke kundens valg,\ndet er sitets eget navn som pluginet kunne læse\". Kun DEN ene værdi\naccepteres fra en klient; alt andet (inkl. udeladt) er 'user' som hidtil.\nUden den ville et godt automatisk gæt blive stemplet som en beslutning\nkunden aldrig har truffet — og `setup.brand_confirm` ville holde op med at\nspørge, netop når den burde.","operationId":"app_upsert_brand_api_apps_brands_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/expertise":{"get":{"tags":["apps"],"summary":"App Get Expertise","description":"Denne installations erklærede ekspertise-emner ({topics: [...]}).\n\nFail-open læsning: en infra-hikke giver et tomt sæt frem for at vælte\nplugin-UI'et.","operationId":"app_get_expertise_api_apps_expertise_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["apps"],"summary":"App Set Expertise","description":"Sæt denne installations ekspertise-emner. Body: {\"topics\": [...]}.\n\nErstatter sættet (ikke en tilføjelse): feltet er ÉN linje i pluginets\nindstillinger, og to skrivemodeller på ét felt er hvordan pluginet og\ndashboardet endte med at være uenige om det sporede navn.\n\nEt TOMT sæt er en gyldig skrivning — det er ejeren, der fjerner sin\nerklæring — og betyder \"gå tilbage til de øvrige kilder\", præcis som et\ntomt felt i pluginet betyder \"publicér ingen knowsAbout\".","operationId":"app_set_expertise_api_apps_expertise_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/actions":{"get":{"tags":["apps"],"summary":"App Actions","description":"AI Command Center-listen for denne installations kunde. Auth: X-Client-Key.\n\nServerer den KANONISKE fix-kø (fix_backlog) projiceret til app-fladernes\nform med engelsk krom. `customer_actions` (action_planner) er fallback for\nkunder hvis backlog endnu ikke er bygget — den dækker kun de 14\ngeo-score-dimensioner med score < 70, og var alene grunden til at kortet\nkunne melde alt klart på et site uden en eneste citationskilde (G-028).\n\nEr listen tom, følger `all_clear` med: de målinger vi tjekkede, med tal.\nFail-open læsning: en fejl giver en tom liste, ikke en 500 i merchantens\nadmin — men så er `all_clear.ok` falsk, og klienten siger ikke alt klart.","operationId":"app_actions_api_apps_actions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/entity-sources":{"get":{"tags":["apps"],"summary":"App Entity Sources","description":"Tier-rangerede profil-kilder + hvilke kunden allerede har.\n\nGuardet af X-Client-Key. Fladet ud til én liste (klienten viser den som en\ntjekliste), men rækkefølgen ER tier-rækkefølgen, og `tier` følger med, så\nen klient aldrig behøver opfinde sin egen prioritering. Fail-open læsning.","operationId":"app_entity_sources_api_apps_entity_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["apps"],"summary":"App Add Entity Source","description":"Gem en bekræftet profil-URL på installationens kunde.\n\nBody: {\"key\": \"linkedin\", \"url\": \"https://linkedin.com/company/…\"}\nSkrivningen er MÅLRETTET: den flette ind i `entity_setup.data.same_as` (og\ni wizardens `state.sameAs`-kort når det findes) og rører intet andet.\nDashboardets egen /entity-setup ERSTATTER hele objektet — den må en klient\naldrig kalde, for så ville et WordPress-gem slette wizardens arbejde.","operationId":"app_add_entity_source_api_apps_entity_sources_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/provision":{"post":{"tags":["apps"],"summary":"Provision Installation","description":"Feltfri selv-provisionering fra en distribueret klient (C1).\n\nBody: {product:'wordpress', external_id:<site host>, domain:<site host>,\n       email:<admin-email, sendt automatisk af klienten>, proof:<token>}\n\nKlienten genererer selv proof-tokenet ved aktivering og eksponerer det på\nsit eget REST-API; backend henter det fra domænet og sammenligner. Ved\nmatch: kunde find-or-create på domænet, FULD onboarding spawnes (KT1 giver\narketype + NACE på rækken før første scan), og per-install-nøglen mintes.\nNøglen returneres præcis én gang.","operationId":"provision_installation_api_apps_provision_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/first-finding":{"get":{"tags":["apps"],"summary":"App First Finding","description":"Det første ÆGTE, navngivne fund — inden for sekunder efter installation.\n\nKører en live, deterministisk schema-probe af kundens eget site via den\nEKSISTERENDE motor (schema_checker.check_schema — aldrig et parallelt\nregelsæt) og kombinerer med registerdata fra kunderækken (KT1 skriver\nNACE/CVR dér ved installation). Ingen LLM, ingen kvote-kost.\n\nÆrlighedskontrakt: kan proben ikke gennemføres, svarer vi {finding: null,\nstatus:'pending'} — ALDRIG et opdigtet fund. Dette er en læse-flade;\nfix-køen ejes fortsat af fix_backlog (ingen parallel backlog).\n\nSelve TEKSTEN bygges i `first_finding.build_finding` — aldrig her, og\naldrig af schema_checker's egne strenge. `?lang=` er SITETS locale\n(klienten sender sin egen `get_locale()`), ikke kundens land: fundet vises\ni sitets admin, så det skal følge sitets sprog. Ukendt sprog → engelsk.\nSe modulets docstring for de fire fejl den opdeling retter.","operationId":"app_first_finding_api_apps_first_finding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/scan-status":{"get":{"tags":["apps"],"summary":"App Scan Status","description":"Hvor står kundens analyse — så en tom flade kan sige HVORFOR den er tom.\n\nLæse-kun. Ingen kredit, ingen kost. Klienten bruger den til at skelne\n\"analysen kører lige nu\" fra \"der er intet at vise, og du kan starte en\".","operationId":"app_scan_status_api_apps_scan_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/rescan":{"post":{"tags":["apps"],"summary":"App Rescan","description":"Start en analyse af kundens eget site fra en distribueret klient.\n\nDelegerer til refresh_customer (samme chokepoint som dashboardets \"Kør\nanalyse igen\"): kost logges én gang, kredit trækkes én gang, watchdoggen\nkan genoptage kørslen. Kredit-loftet slår igennem som 402 — det er\nopgraderings-øjeblikket, ikke en fejl der skal skjules.","operationId":"app_rescan_api_apps_rescan_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/embed":{"get":{"tags":["apps"],"summary":"App Embed Status","description":"Må dette site få Radars widget, og har det allerede en nøgle?","operationId":"app_embed_status_api_apps_embed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["apps"],"summary":"App Embed Issue","description":"Udsted (eller roter) sitets widget-nøgle. Vises ÉN gang.","operationId":"app_embed_issue_api_apps_embed_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["apps"],"summary":"App Embed Revoke","description":"Sluk: tilbagekald KUN den nøgle, dette site selv har fået.","operationId":"app_embed_revoke_api_apps_embed_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/entitlements":{"get":{"tags":["entitlements"],"summary":"Me Entitlements","operationId":"me_entitlements_api_me_entitlements_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/workspaces":{"get":{"tags":["me"],"summary":"Me Workspaces","operationId":"me_workspaces_api_me_workspaces_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/workspaces/{customer_id}":{"get":{"tags":["me"],"summary":"Me Workspace Get","operationId":"me_workspace_get_api_me_workspaces__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["me"],"summary":"Me Workspace Patch","operationId":"me_workspace_patch_api_me_workspaces__customer_id__patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspacePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/scan-config":{"get":{"tags":["me"],"summary":"Me Scan Config Get","operationId":"me_scan_config_get_api_me_scan_config_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["me"],"summary":"Me Scan Config Patch","operationId":"me_scan_config_patch_api_me_scan_config_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanConfigPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/profile":{"get":{"tags":["me"],"summary":"Me Profile Get","operationId":"me_profile_get_api_me_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"patch":{"tags":["me"],"summary":"Me Profile Patch","operationId":"me_profile_patch_api_me_profile_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/onboarding":{"get":{"summary":"Onboarding Status Endpoint","description":"Check if the user has completed onboarding.\n\nOpen endpoint — Lovable handles access control.","operationId":"onboarding_status_endpoint_api_me_onboarding_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Onboarding Complete Endpoint","description":"Mark onboarding as completed.\n\nOpen endpoint — Lovable handles access control.","operationId":"onboarding_complete_endpoint_api_me_onboarding_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"patch":{"tags":["me"],"summary":"Me Onboarding Patch","operationId":"me_onboarding_patch_api_me_onboarding_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingPatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/team":{"get":{"tags":["me"],"summary":"Me Team","operationId":"me_team_api_me_team_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/team/invite":{"post":{"tags":["me"],"summary":"Me Team Invite","operationId":"me_team_invite_api_me_team_invite_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamInvite"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/team/members/{member_id}":{"patch":{"tags":["me"],"summary":"Me Team Member Patch","description":"Change a member's role. Owner only. The owner's own row is immutable.","operationId":"me_team_member_patch_api_me_team_members__member_id__patch","parameters":[{"name":"member_id","in":"path","required":true,"schema":{"type":"string","title":"Member Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberRolePatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["me"],"summary":"Me Team Member Delete","description":"Remove a member (or leave a workspace yourself). The owner row is immutable.\n\nRules: owner may remove any non-owner member; admin may remove viewers and\nthemselves; viewer may only remove themselves (leave).","operationId":"me_team_member_delete_api_me_team_members__member_id__delete","parameters":[{"name":"member_id","in":"path","required":true,"schema":{"type":"string","title":"Member Id"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/account":{"delete":{"tags":["me"],"summary":"Me Account Delete","description":"Self-service account deletion.\n\nGuard rails:\n  - body.confirm must be exactly \"DELETE\" (frontend shows a type-to-confirm UI)\n  - blocked if the user OWNS a workspace with an active paid plan (409)\n  - blocked if an owned workspace still has other members (409) — remove\n    or transfer them first\n  - owned workspaces without plan/members are deleted along with the account","operationId":"me_account_delete_api_me_account_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDelete"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/billing/status":{"get":{"tags":["me"],"summary":"Me Billing Status","operationId":"me_billing_status_api_me_billing_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/customers/{customer_id}/pairing":{"post":{"tags":["me"],"summary":"Me Create Pairing Code","description":"Udsted en engangs-parringskode til eget workspace (bruger-JWT).\n\nKoden returneres i KLARTEKST én gang — kun hashen gemmes, så den kan aldrig\nhentes frem igen. Ejer/admin, fordi koden veksles til en installationsnøgle\nder læser hele workspacets data; en viewer må se status, ikke udstede adgang.","operationId":"me_create_pairing_code_api_me_customers__customer_id__pairing_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/PairingRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/claim-site":{"post":{"tags":["me"],"summary":"Me Claim Site","description":"Indløs et konto-claim-token (P1.1 — den omvendte parringskode).\n\nEn provision-oprettet kunde er uadopteret; pluginet/rapport-mailen bærer et\nclaim-link til platform.geoa.app/claim. Brugeren logger ind dér, og dette\nendpoint veksler tokenet til et user_customers-ejerlink — hvorefter\nplan-arven kører (apply_inherited_plan_to_customer inde i linkeren).\n\nRækkefølge med vilje: LINK FØRST, forbrug tokenet BAGEFTER — et transient\nlink-fejlslag må ikke brænde tokenet (re-provision ville ellers være\neneste vej til et nyt). Tokenet er stadig single-use: forbruget er en\natomisk status-PATCH, og taberen af et racende dobbelt-claim rammer\none-owner-indekset i linkeren (owned_by_active_user).","operationId":"me_claim_site_api_me_claim_site_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimSiteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/customers/{customer_id}/installations":{"get":{"tags":["me"],"summary":"Me List Installations","description":"Hvilke sites/shops er parret med dette workspace (bruger-JWT).\n\nEthvert medlem må se forbindelsesstatus — det er ikke en hemmelighed, og\nUI'et skal kunne sige \"dit site er allerede forbundet\" før man udsteder en\nny kode. Nøgler (hash såvel som klartekst) forlader aldrig backenden.","operationId":"me_list_installations_api_me_customers__customer_id__installations_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/customers/{customer_id}/installations/{installation_id}":{"delete":{"tags":["me"],"summary":"Me Revoke Installation","description":"Afbryd ét forbundet site (bruger-JWT).\n\nEjer/admin — det er en destruktiv handling: sitet mister sin nøgle og holder\nop med at synke fixes og artikler. En viewer må se forbindelsen, ikke rive\nden ned.\n\nModstykket til /api/apps/revoke (HERMES), som tager et vilkårligt\ninstallation_id og stoler fuldstændigt på kalderen. Her afgrænses PATCH'en\ntil kundens egne rækker, så et id fra et andet workspace ikke kan rammes —\nheller ikke ved en fejl i frontenden.","operationId":"me_revoke_installation_api_me_customers__customer_id__installations__installation_id__delete","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"installation_id","in":"path","required":true,"schema":{"type":"string","title":"Installation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/workspaces/{customer_id}/transfer-ownership":{"post":{"tags":["me"],"summary":"Me Workspace Transfer Ownership","operationId":"me_workspace_transfer_ownership_api_me_workspaces__customer_id__transfer_ownership_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnershipTransfer"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/workspaces/{customer_id}/invite-installer":{"post":{"tags":["me"],"summary":"Me Invite Installer","operationId":"me_invite_installer_api_me_workspaces__customer_id__invite_installer_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallerInvite"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assistant/chat":{"post":{"tags":["assistant"],"summary":"Assistant Chat","operationId":"assistant_chat_api_assistant_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support/ticket":{"post":{"tags":["assistant"],"summary":"Support Ticket","description":"Opret en supportsag UDEN at gå gennem assistenten.\n\nHvorfor ruten findes, når chatten allerede kan det: fejler assistenten,\nfejler supportkanalen med den. Det er præcis det øjeblik, hvor brugeren\nhar allermest brug for at kunne råbe op — en supportvej, der kun virker,\nnår produktet virker, er ingen supportvej. Sentrys feedback-widget er\npermanent frem for fejludløst af samme grund.\n\nPanelet viser derfor en \"send alligevel\"-knap, når streamen fejler, og\nden lander her. Samme sag, samme snapshot, samme kvittering — kun\n`source` skelner ('direct' mod 'assistant'), og den skelnen er værd at\nkunne se: er 'direct' pludselig i overtal, er assistenten selv nede.","operationId":"support_ticket_api_support_ticket_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assistant/feedback":{"post":{"tags":["assistant"],"summary":"Assistant Feedback","description":"Tommel op/ned på det seneste svar i en samtale.\n\nHvorfor det er værd at bygge nu, med nul kunder: uden det kan ingen se OM\nassistenten svarer godt — kun at den svarede. Radar havde det; de to andre\nhavde ikke, og derfor har vi i dag ingen anelse om kvaliteten dér.\n\nVurderingen sættes på den SIDSTE assistent-besked i samtalen. Frontenden\nbehøver derfor ikke kende besked-id'er, og der kan ikke opstå tvivl om\nhvilket svar en tommel gælder.","operationId":"assistant_feedback_api_assistant_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assistant/conversations":{"get":{"tags":["assistant"],"summary":"List Conversations","description":"Brugerens egne samtaler, nyeste først. Titel = første spørgsmål.","operationId":"list_conversations_api_assistant_conversations_get","parameters":[{"name":"product","in":"query","required":false,"schema":{"type":"string","default":"","title":"Product"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assistant/conversations/{conversation_id}":{"get":{"tags":["assistant"],"summary":"Get Conversation","description":"Én samtales beskeder i rækkefølge — til 'fortsæt hvor du slap'.\n\nSvarformatet spejler chat-endpointets vokabular: role/content/sources/\nrating, så frontenden kan genindlæse tråden direkte i sin state.","operationId":"get_conversation_api_assistant_conversations__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/export":{"get":{"tags":["me"],"summary":"Export My Data","description":"Art. 15/20-eksport af backend-dataene for brugerens egne workspaces.","operationId":"export_my_data_api_me_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/activation":{"get":{"summary":"Activation Funnel","description":"Aktiveringstragten pr. kanal og pr. uge — fremmede adskilt fra egne.\n\nSvarform (additiv kontrakt):\n  { funnel: {<channel>: {<step>: n}}, weekly: {<uge>: {...}},\n    foreign: {...}, own: {...}, ttv_seconds: {median, n},\n    archetype_basis: {nace, signals, default}, own_domains_n, weeks }","operationId":"activation_funnel_admin_activation_get","parameters":[{"name":"weeks","in":"query","required":false,"schema":{"type":"integer","default":8,"title":"Weeks"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/prompts":{"get":{"tags":["radar"],"summary":"Radar Prompts","operationId":"radar_prompts_api_growth_radar_prompts_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["radar"],"summary":"Radar Add Prompt","operationId":"radar_add_prompt_api_growth_radar_prompts_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/usage":{"get":{"tags":["radar"],"summary":"Radar Usage","description":"Forbrugs-endpointet (fase 1.2): plan, grænser og aktuelt forbrug i én\nkrop, så UI'et kan tegne \"X af Y\"-målere og upgrade-øjeblikket aldrig er\nen overraskelse (money-flade #4).","operationId":"radar_usage_api_growth_radar_usage_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/prompts/{prompt_id}":{"delete":{"tags":["radar"],"summary":"Radar Delete Prompt","operationId":"radar_delete_prompt_api_growth_radar_prompts__prompt_id__delete","parameters":[{"name":"prompt_id","in":"path","required":true,"schema":{"type":"string","title":"Prompt Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/prompts/{prompt_id}/results":{"get":{"tags":["radar"],"summary":"Radar Prompt Results","operationId":"radar_prompt_results_api_growth_radar_prompts__prompt_id__results_get","parameters":[{"name":"prompt_id","in":"path","required":true,"schema":{"type":"string","title":"Prompt Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/keywords/convert":{"post":{"tags":["radar"],"summary":"Radar Keywords Convert","operationId":"radar_keywords_convert_api_growth_radar_keywords_convert_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeywordsConvert"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/first-run":{"post":{"tags":["radar"],"summary":"Radar First Run","description":"Styret første-kørsel: ét klik → foreslåede prompts oprettet → første\ntjek affyret. \"Aha\" på under 60 sekunder uden at læse noget.\n\nForslagene bygges af den viden huset ALLEREDE har (customer_knowledge:\nservices + branche) gennem market_locale-skabelonerne — samme kæde som\nkeywords/convert, bare uden at kunden skal taste noget. Er der ingen\nbrugbar viden, svarer vi ærligt needs_keywords i stedet for at så\ngeneriske prompts, der måler ingenting (ingen tomme kasser).\n\nIdempotent: workspaces med eksisterende prompts får already_started.\nPlan-bevidst: antal forslag = min(5, pakkens prompt-grænse) — gratis\nplan får 3 ægte prompts, ikke en mur (aktivering før betaling).","operationId":"radar_first_run_api_growth_radar_first_run_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/competitors":{"get":{"tags":["radar"],"summary":"Radar Competitors","operationId":"radar_competitors_api_growth_radar_competitors_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["radar"],"summary":"Radar Add Competitor","operationId":"radar_add_competitor_api_growth_radar_competitors_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetitorCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/competitors/{competitor_id}":{"delete":{"tags":["radar"],"summary":"Radar Delete Competitor","operationId":"radar_delete_competitor_api_growth_radar_competitors__competitor_id__delete","parameters":[{"name":"competitor_id","in":"path","required":true,"schema":{"type":"string","title":"Competitor Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/competitors/{competitor_id}/compare":{"get":{"tags":["radar"],"summary":"Radar Competitor Compare","operationId":"radar_competitor_compare_api_growth_radar_competitors__competitor_id__compare_get","parameters":[{"name":"competitor_id","in":"path","required":true,"schema":{"type":"string","title":"Competitor Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/stats":{"get":{"tags":["radar"],"summary":"Radar Stats","operationId":"radar_stats_api_growth_radar_stats_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/history":{"get":{"tags":["radar"],"summary":"Radar History","operationId":"radar_history_api_growth_radar_history_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/reports/monthly":{"post":{"tags":["radar"],"summary":"Radar Monthly Report","operationId":"radar_monthly_report_api_growth_radar_reports_monthly_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/alerts":{"get":{"tags":["radar"],"summary":"Radar Alerts Get","operationId":"radar_alerts_get_api_growth_radar_alerts_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["radar"],"summary":"Radar Alerts Patch","operationId":"radar_alerts_patch_api_growth_radar_alerts_patch","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertSettingsPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/radar/alerts/test":{"post":{"tags":["radar"],"summary":"Radar Alerts Test","operationId":"radar_alerts_test_api_growth_radar_alerts_test_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/searches":{"post":{"tags":["prospecting"],"summary":"Create Search","operationId":"create_search_api_growth_prospecting_searches_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["prospecting"],"summary":"List Searches","operationId":"list_searches_api_growth_prospecting_searches_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/searches/{search_id}":{"get":{"tags":["prospecting"],"summary":"Get Search","operationId":"get_search_api_growth_prospecting_searches__search_id__get","parameters":[{"name":"search_id","in":"path","required":true,"schema":{"type":"string","title":"Search Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/searches/{search_id}/companies":{"get":{"tags":["prospecting"],"summary":"Search Companies","operationId":"search_companies_api_growth_prospecting_searches__search_id__companies_get","parameters":[{"name":"search_id","in":"path","required":true,"schema":{"type":"string","title":"Search Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"qualified","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Qualified"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/searches/{search_id}/score":{"post":{"tags":["prospecting"],"summary":"Score Search","operationId":"score_search_api_growth_prospecting_searches__search_id__score_post","parameters":[{"name":"search_id","in":"path","required":true,"schema":{"type":"string","title":"Search Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/searches/{search_id}/requalify":{"post":{"tags":["prospecting"],"summary":"Requalify Search","description":"Kør registeropslaget igen for kandidater uden registersvar — fx efter\net Apiverket-udfald. Koster hverken Brave-kald eller nye rækker.","operationId":"requalify_search_api_growth_prospecting_searches__search_id__requalify_post","parameters":[{"name":"search_id","in":"path","required":true,"schema":{"type":"string","title":"Search Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/prospecting/companies/{company_id}/promote":{"post":{"tags":["prospecting"],"summary":"Promote Company","operationId":"promote_company_api_growth_prospecting_companies__company_id__promote_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations":{"get":{"tags":["radar-integrations"],"summary":"Get Integrations","operationId":"get_integrations_api_growth_integrations_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations/webhook":{"put":{"tags":["radar-integrations"],"summary":"Put Webhook","operationId":"put_webhook_api_growth_integrations_webhook_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations/webhook/test":{"post":{"tags":["radar-integrations"],"summary":"Test Webhook","operationId":"test_webhook_api_growth_integrations_webhook_test_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations/hubspot":{"put":{"tags":["radar-integrations"],"summary":"Put Hubspot","operationId":"put_hubspot_api_growth_integrations_hubspot_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["radar-integrations"],"summary":"Delete Hubspot","operationId":"delete_hubspot_api_growth_integrations_hubspot_delete","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations/pipedrive":{"put":{"tags":["radar-integrations"],"summary":"Put Pipedrive","operationId":"put_pipedrive_api_growth_integrations_pipedrive_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["radar-integrations"],"summary":"Delete Pipedrive","operationId":"delete_pipedrive_api_growth_integrations_pipedrive_delete","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/integrations/calendar":{"put":{"tags":["radar-integrations"],"summary":"Put Calendar","operationId":"put_calendar_api_growth_integrations_calendar_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/config":{"get":{"tags":["embed-assistant"],"summary":"Embed Config","operationId":"embed_config_embed_config_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/chat":{"post":{"tags":["embed-assistant"],"summary":"Embed Chat","operationId":"embed_chat_embed_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedChatBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/lead":{"post":{"tags":["embed-assistant"],"summary":"Embed Lead","operationId":"embed_lead_embed_lead_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedLeadBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/embed/install":{"post":{"tags":["embed-assistant"],"summary":"Create Embed Install","description":"Mint widget-nøglen (vises ÉN gang) + returnér script-snippet.\nGenkald roterer: gamle nøgler revokeres (mint-hjælperens kontrakt).","operationId":"create_embed_install_api_growth_embed_install_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["embed-assistant"],"summary":"Get Embed Install","operationId":"get_embed_install_api_growth_embed_install_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["embed-assistant"],"summary":"Revoke Embed Install","operationId":"revoke_embed_install_api_growth_embed_install_delete","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/assistant.js":{"get":{"tags":["embed-assistant"],"summary":"Embed Widget Js","operationId":"embed_widget_js_embed_assistant_js_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/embed/events":{"post":{"tags":["embed-live-score"],"summary":"Embed Events","operationId":"embed_events_embed_events_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventsBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/embed/live-score/settings":{"get":{"tags":["embed-live-score"],"summary":"Get Live Settings","operationId":"get_live_settings_api_growth_embed_live_score_settings_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["embed-live-score"],"summary":"Put Live Settings","operationId":"put_live_settings_api_growth_embed_live_score_settings_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveSettingsBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/embed/live-score/sessions":{"get":{"tags":["embed-live-score"],"summary":"List Live Sessions","description":"Listen over varme besøgende. ``count`` er udledt af PRÆCIS det array,\nder returneres (CLAUDE.md: et tal over en liste udledes af listen).","operationId":"list_live_sessions_api_growth_embed_live_score_sessions_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":720,"minimum":1,"default":24,"title":"Hours"}},{"name":"hot_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Hot Only"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"en","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/embed/live-score/lift":{"get":{"tags":["embed-live-score"],"summary":"Live Lift","operationId":"live_lift_api_growth_embed_live_score_lift_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/embed/live-score/usage":{"get":{"tags":["embed-live-score"],"summary":"Live Usage","description":"Forbrugsmåleren: alarmer denne måned mod pakkens loft.","operationId":"live_usage_api_growth_embed_live_score_usage_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/visit":{"post":{"tags":["visitor-id"],"summary":"Embed Visit","description":"Anonymiseret hit fra embed-scriptet. UDEN flaget: ren no-op — der\npersisteres INTET, og IP'en behandles ikke (204 før alt andet).","operationId":"embed_visit_embed_visit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/visitors":{"get":{"tags":["visitor-id"],"summary":"List Visitors","operationId":"list_visitors_api_growth_visitors_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/visitors/settings":{"put":{"tags":["visitor-id"],"summary":"Put Visitor Settings","description":"Betingelse 2: aktivering kræver aktivt opt-in + bekræftelse af at\nprivatlivsteksten er indsat (tidsstemplet — dokumenterbar). Deaktivering\ner altid mulig. Betingelse 4: DE/AT kan ikke aktivere.","operationId":"put_visitor_settings_api_growth_visitors_settings_put","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitorSettingsBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-scan/run":{"post":{"tags":["market-scan"],"summary":"Start Market Scan","description":"Start en markeds-scanning (baggrund). HERMES-beskyttet.","operationId":"start_market_scan_api_market_scan_run_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-scan/run/status":{"get":{"tags":["market-scan"],"summary":"Market Scan Status","description":"Status for igangværende/seneste kørsler. HERMES-beskyttet.","operationId":"market_scan_status_api_market_scan_run_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/market-scan/match-probe":{"post":{"tags":["market-scan"],"summary":"Match Probe","description":"MÅLESONDE: hvor mange CVR-virksomheder kan vi finde et website til via GBP?\n\nBesvarer det åbne spørgsmål bag den repræsentative benchmark: SERP-discovery\nfinder kun branchens MEST synlige. CVR-navnesøgning giver derimod et udtræk\nder er ukorreleret med digital modenhed (inkl. den lange hale), men CVR har\nintet website-felt. Google Business Profile HAR et — sonden måler hvor stor\nen andel der reelt kan matches, så vi kan beslutte på tal frem for skøn.\n\nHERMES-beskyttet og hårdt cappet (kost ~$0,002 pr. opslag). Skriver INTET\ntil databasen — den måler kun.","operationId":"match_probe_api_market_scan_match_probe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProbeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-scan/stats":{"get":{"tags":["market-scan"],"summary":"Market Scan Stats","description":"Offentlige branche-aggregater (fodrer gratis-værktøjet). Kun tal, aldrig domæner.","operationId":"market_scan_stats_api_market_scan_stats_get","parameters":[{"name":"industry","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Industry"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","minLength":2,"maxLength":2,"default":"dk","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-scan/city-stats":{"get":{"tags":["market-scan"],"summary":"Market Scan City Stats","description":"Offentlige BY-aggregater. Kun tal, aldrig domæner — samme regel som /stats.","operationId":"market_scan_city_stats_api_market_scan_city_stats_get","parameters":[{"name":"city","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"City"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","minLength":2,"maxLength":2,"default":"dk","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-scan/integrity":{"get":{"tags":["market-scan"],"summary":"Market Scan Integrity","description":"Hvilke rækker i panelet er sandsynligvis IKKE virksomheder i branchen?\n\nHERMES-beskyttet — den returnerer domæner og firmanavne, og de virksomheder\nhar ikke bedt om at blive målt (samme privacy-regel som /results).\n\nSletter aldrig noget. Den erstatter det manuelle gennemsyn af 3.425 rækker\nmed en kort, begrundet liste — afgørelsen er stadig menneskets.","operationId":"market_scan_integrity_api_market_scan_integrity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/market-ai/run":{"post":{"tags":["market-ai"],"summary":"Start Market Ai Scan","description":"Start en branche-AI-scanning (baggrund). HERMES-beskyttet. KOSTER penge.","operationId":"start_market_ai_scan_api_market_ai_run_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIScanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-ai/run/status":{"get":{"tags":["market-ai"],"summary":"Market Ai Status","operationId":"market_ai_status_api_market_ai_run_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/market-ai/results":{"get":{"tags":["market-ai"],"summary":"Market Ai Results","description":"Fulde resultater INKL. domæner — HERMES-beskyttet, aldrig offentligt.","operationId":"market_ai_results_api_market_ai_results_get","parameters":[{"name":"industry","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Industry"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","minLength":2,"maxLength":2,"default":"dk","title":"Country"}},{"name":"run_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-ai/stats":{"get":{"tags":["market-ai"],"summary":"Market Ai Stats","description":"Offentlige aggregater — KUN tal, aldrig domæner eller firmanavne.\n\nVirksomhederne er ikke kunder og har ikke bedt om at blive målt; derfor\nN_MIN-gulv og ingen identifikation af enkeltvirksomheder.","operationId":"market_ai_stats_api_market_ai_stats_get","parameters":[{"name":"industry","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Industry"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","minLength":2,"maxLength":2,"default":"dk","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/claim-watchdog/run":{"post":{"tags":["claim-watchdog"],"summary":"Run Now","description":"Kør alle tjek nu. HERMES-beskyttet. Gratis (kun DNS + HTTP).","operationId":"run_now_api_claim_watchdog_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/claim-watchdog/status":{"get":{"tags":["claim-watchdog"],"summary":"Status","description":"Nyeste tjek pr. konkurrent × type + hvad der kræver manuel gennemgang.\n\nHERMES-beskyttet: det er en intern drifts-flade, ikke en offentlig påstand.","operationId":"status_api_claim_watchdog_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/enforce-flags":{"get":{"summary":"Get Enforce Flags","description":"Hvad håndhæver i DETTE miljø? Ingen værdier lækkes — kun tændt/slukket.\n\nBrug den, før du planlægger ud fra et flag. Den svarer for den proces,\nkaldet rammer: prod, hvis du kalder api.geoa.app.","operationId":"get_enforce_flags_api_admin_enforce_flags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/data-claims/run":{"post":{"tags":["data-claims"],"summary":"Run Now","description":"Genberegn alle publicerede tal nu. HERMES-beskyttet. Gratis.","operationId":"run_now_api_data_claims_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/data-claims/status":{"get":{"tags":["data-claims"],"summary":"Status","description":"Nyeste tjek pr. påstand + hvad der kræver menneskelig stillingtagen.","operationId":"status_api_data_claims_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/inbound/email":{"post":{"tags":["inbound-email"],"summary":"Receive Inbound Email","description":"Modtag et svar fra Resend Inbound. Se modulets sikkerhedsmodel.","operationId":"receive_inbound_email_api_inbound_email_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/employee-inbox":{"get":{"tags":["inbound-email"],"summary":"List Employee Inbox","description":"Kundens beskeder til medarbejderen, nyeste først — skrivebordets\nkommende \"beskeder\"-flade + Ask AI-hjernens kontekst (fase F).","operationId":"list_employee_inbox_api_customers__customer_id__employee_inbox_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/workspaces/{customer_id}/postbox":{"get":{"tags":["postbox"],"summary":"Get Postbox","description":"Kundens postkasse-adresse. Har kunden ingen endnu, tildeles én ud fra\ndomænet — adressen skal kunne vises fra første skærm, ikke først når\nnogen har fundet en indstilling.","operationId":"get_postbox_api_me_workspaces__customer_id__postbox_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["postbox"],"summary":"Patch Postbox","description":"Skift adressen og/eller hvilke SoMe-kanaler, der laves udkast til.","operationId":"patch_postbox_api_me_workspaces__customer_id__postbox_patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostboxPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/employee-suggestions":{"get":{"tags":["employee-suggestions"],"summary":"List Suggestions","description":"Åbne (og nyligt afgjorte) forslag — 'Venter på dig'-fladens kilde.","operationId":"list_suggestions_api_customers__customer_id__employee_suggestions_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/employee-suggestions/{suggestion_id}/approve":{"post":{"tags":["employee-suggestions"],"summary":"Approve Suggestion","description":"Kundens klik — den eneste fuldmagt et chat-forslag kan få.","operationId":"approve_suggestion_api_customers__customer_id__employee_suggestions__suggestion_id__approve_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"suggestion_id","in":"path","required":true,"schema":{"type":"string","title":"Suggestion Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/employee-suggestions/{suggestion_id}/dismiss":{"post":{"tags":["employee-suggestions"],"summary":"Dismiss Suggestion","operationId":"dismiss_suggestion_api_customers__customer_id__employee_suggestions__suggestion_id__dismiss_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"suggestion_id","in":"path","required":true,"schema":{"type":"string","title":"Suggestion Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/tone-example":{"post":{"tags":["employee-suggestions"],"summary":"Add Tone Example","description":"Kundens redigerede formulering som varigt tone-input.\n\nKaldes når kunden svarer JA til \"Skal jeg skrive sådan fremover?\" efter\nen Ret først-redigering. Teksten lægges i brand_memory.data.tone_examples\n— MENNESKE-laget, som allerede injiceres i alle genererings-prompts\n(content_studio.BRAND_MEMORY_SECTIONS) og vinder over register/scrape.\nEget felt med vilje: kundens håndskrevne data.tone må aldrig clobbres.\n\nAuth: HERMES eller kundens egen JWT (_authorize_dashboard_write).\nNyeste først, dubletter fjernes, loft på 5 (ældste ryger).","operationId":"add_tone_example_api_customers__customer_id__tone_example_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/resend":{"post":{"tags":["email"],"summary":"Resend Webhook","operationId":"resend_webhook_webhooks_resend_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resend Webhook Webhooks Resend Post"}}}}}}},"/admin/email-events":{"get":{"tags":["email"],"summary":"Email Events","description":"De seneste kvitteringer — så det kan SES at webhooken virker.\n\nHERMES-beskyttet: rækkerne indeholder modtageradresser.","operationId":"email_events_admin_email_events_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"event_type","in":"query","required":false,"schema":{"type":"string","default":"","title":"Event Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes":{"get":{"tags":["fixes"],"summary":"List Fixes","description":"List kundens fixes + installations-/implementeringsstatus.\n\n'superseded' udelades som DEFAULT. Det er et internt revisionsspor: hver\ngang kildeindholdet ændrer sig (llms.txt vokser, schema beriges) skriver\n_auto_insert_pending et nyt pending og markerer det forrige superseded.\nRækkerne kom med i dette svar, og dashboardet viste dem som handlingsbare\nfixes — geoa.app stod 2026-08-06 med 2 pending og 13 superseded, dvs. ét\nreelt llms.txt-fix præsenteret som syv \"kopiér prompt\"-kort (samme mønster\nhos matas.dk 11, relark.dk 10, danskebank.dk 8).\nRevisionssporet hentes med include_superseded=true eller status=superseded.","operationId":"list_fixes_api_customers__customer_id__fixes_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"include_superseded","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Superseded"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/internal-links":{"get":{"tags":["fixes"],"summary":"Internal Links Report","description":"Orphan-rapporten (intern-link-motoren, 2026-08-24).\n\nLæser hygiejne-sweepets gemte fund: sider uden ét indgående internt link\n(code=orphan_page på sidens egen række) + link-forslag (kildeside +\nanchor) og status på de materialiserede internal_link-fixes. Additivt —\nden gamle jina-audit på /internal-links/{id} er urørt.","operationId":"internal_links_report_api_customers__customer_id__internal_links_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/notfound-log":{"post":{"tags":["fixes"],"summary":"Report Notfound Log","description":"Klienten melder 404-trafik hjem (X-Client-Key — ALDRIG HERMES).\n\nBody: {entries: [{path, hits, sources?: {self, bot, other}}]} — max 50\npr. kald. `sources` er additiv (plugin ≥ 0.9.62, grove kategorier, aldrig\nIP/UA); fix-maskinen tæller kun hits, der ikke er `self`. Hits AKKUMULERES pr.\n(kunde, sti); klienten nulstiller sin lokale tæller efter succes\n(delta-semantik). Bot-prober (wp-login, .env, .php …) filtreres, og\ntabellen cappes pr. kunde (laveste hits ryddes) så bot-støj aldrig kan\nvokse frit. Fail-open: valideringsfejl svarer 200 med accepted=0.","operationId":"report_notfound_log_api_apps_notfound_log_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/bot-policy":{"post":{"tags":["fixes"],"summary":"Set Bot Policy","description":"Pluginets AI-bot-toggle (X-Client-Key — ALDRIG HERMES).\n\nBody: {block_training: true|false}. Merchantens klik i eget wp-admin ER\ngodkendelsen (samme princip som report-applied: selv-anvendt i egen\nbutik), så fixet skrives approved og rammer feedet med det samme.\nReglerne bygges HER af backendens kanoniske liste\n(auto_fix_engine.TRAINING_BOT_TOKENS) — pluginet må aldrig eje bot-lister\n(wp-plugin-doktrinen), og citations-bots kan aldrig blokeres, uanset\nhvad klienten sender. false = eksplicit alt-tilladt-valg (blocked_bots\ntom) — valget er registreret, ikke slettet.","operationId":"set_bot_policy_api_apps_bot_policy_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/notfound":{"get":{"tags":["fixes"],"summary":"Notfound Report","description":"404-rapporten (HERMES): døde stier fra egne målinger + pluginets\n404-log, med deterministiske mål-forslag og status på redirect-fixes.\n\nHver række bærer ADDITIVT `recheck` (dead/alive/unknown, målt live nu) og\n`platform_path`. Rapporten er den tredje flade på de samme historiske\ndata, og en flade der viser en løst 404 som stadig død, sender læseren\nvidere til den samme skadelige handling som kortet gjorde.","operationId":"notfound_report_api_customers__customer_id__notfound_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/cloudflare-check":{"post":{"tags":["fixes"],"summary":"Cloudflare Check","description":"Tjek om kundens domæne allerede er bag Cloudflare (cf-ray-header).\n\nBruges i onboarding til at anbefale Worker (Cloudflare-kunder) vs.\nWordPress-plugin (alle andre). Resultatet gemmes på kunden og eksponeres\nadditivt i GET /api/customers/{id}/fixes som \"cloudflare\".","operationId":"cloudflare_check_api_customers__customer_id__cloudflare_check_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/detect-cms":{"post":{"tags":["fixes"],"summary":"Detect Cms Endpoint","description":"Detektér kundens CMS (gratis direkte HTML-fetch, ingen AI/Jina).\n\nPersisterer customers.platform + cms_confidence + cms_detected_at\n(migration-047). Bruger-bekræftet platform (confidence >= 0.99) røres\naldrig. Mønster = cloudflare-check; bruges af onboarding + \"detektér igen\".","operationId":"detect_cms_endpoint_api_customers__customer_id__detect_cms_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/integration":{"get":{"tags":["fixes"],"summary":"Integration Status","description":"Én samlet forbindelses-/sync-status pr. kunde — platform-agnostisk.\n\nAbstraherer de to integrations-modeller bag ét interface:\n  PULL (X-Client-Key-klienter): last_sync = max(app_installations.\n    last_seen_at, app_install_reports.reported_at) — stemples ved feed-\n    hentning og status-ping (~hver time).\n  PUSH (shopify/seo-proof, HERMES): last_sync = max(last_reported_live_at)\n    for approved fixes med den source — der ER ingen heartbeat, så\n    sundheden lover kun \"sidst bekræftet\".\n\nSvar: {cms, integrations[], recommended_channel, health}. Additivt —\neksisterende endpoints uændrede. Ny platform = ny VALID_PRODUCTS-entry;\ndette endpoint kræver ingen ændring.","operationId":"integration_status_api_customers__customer_id__integration_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/plan-sync":{"post":{"tags":["fixes"],"summary":"Channel Plan Sync","description":"Kanal-spejling af plan for marketplace-fakturerede kunder (HERMES).\n\nseo-proof (Shopify Managed Pricing) melder sin plan ind her, så\ndashboardet og entitlements viser det samme som appen. Body:\n{plan: none|starter|pro|business, status?: active|trialing|canceled|\npast_due, source?: \"shopify\"}. Kun vores egne trusted servers kalder\ndette — aldrig distribuerede klienter (de har X-Client-Key, ikke HERMES).\nAdditivt: Lovable/Stripe-syncen (/api/internal/plan-sync) er uændret.","operationId":"channel_plan_sync_api_customers__customer_id__plan_sync_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/prepare":{"post":{"tags":["fixes"],"summary":"Prepare Fix","description":"Materialisér et fix server-side som 'pending' (afventer godkendelse).\n\nAuth (2026-08-26, \"Ret først\"-flowet): HERMES-nøglen (backend/cron)\nELLER den indloggede kundes Supabase-JWT via _authorize_dashboard_write —\nsamme model som approve/reject. Før var ruten HERMES-only, og browserens\nredigér-og-gem-vej ville enten ramme 401 eller (kaldt med server-nøglen\nfra en offentlig serverFn) omgå ejerskabstjekket.\n\nBody: {fix_type: 'llms_txt'|'schema_jsonld'|'meta', url_pattern?: '/',\n       payload?: {...}}  — payload kræves kun for 'meta'\n       ({title?, description?}; mindst ét af dem). For 'llms_txt' er\n       payload {content} valgfri: kundens redigerede fil (\"Ret først\"),\n       valideret som llms.txt — ellers genereres den.\nIndhold genereres altid i backenden:\n  llms_txt      → seneste llms_txt_configs-række (409 hvis ingen — kør\n                  POST /llms-txt/generate/{customer_id} først)\n  schema_jsonld → schema_builder.build_customer_schema (CVR-verificeret,\n                  match-kvalitetsgatet)\n  meta          → title/description fra payload (dashboard-forslag)\nEr indholdet uændret ift. et aktivt (pending/approved) fix af samme\ntype+pattern, genbruges den række (`reused: true`) — ellers superseded'es\ntidligere pending, og der skrives en ny.","operationId":"prepare_fix_api_customers__customer_id__fixes_prepare_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/{fix_id}/preview":{"get":{"tags":["fixes"],"summary":"Preview Fix","description":"Hvad ÆNDRER sig på sitet, hvis dette fix godkendes — ikke bare hvad der\nbliver publiceret.\n\nPreviewet viste før kun den JSON-LD vi ville skrive. En merchant kan ikke\nse ud fra den blok, om den tilføjer noget, erstatter noget verificeret\neller er identisk med det der allerede står — og det var netop dét, der\ngjorde wonmetrics-fejlen usynlig ved godkendelsen (dogfood 2026-08-30).\n\nSvaret er felt-for-felt: `diff.added/changed/removed/unchanged` mod sitets\nNUVÆRENDE Organization-node, plus `stale: true` når køens gemte indhold\nikke længere er dét, vi ville publicere (godkendelsen opfrisker selv).\n\nAuth: HERMES ELLER den indloggede ejers JWT — samme som approve/reject,\nfordi ruten laver et udgående hent på vores regning.","operationId":"preview_fix_api_customers__customer_id__fixes__fix_id__preview_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/{fix_id}/approve":{"post":{"tags":["fixes"],"summary":"Approve Fix","description":"Godkend et fix → det indgår i klient-feedet. Superseder tidligere godkendt\nfix af samme type+url_pattern (kun ét aktivt ad gangen).\n\nAuth: HERMES-nøgle (backend) ELLER den indloggede kundes Supabase-JWT\n(Authorization: Bearer) hvis brugeren ejer kunden — se _authorize_dashboard_write.","operationId":"approve_fix_api_customers__customer_id__fixes__fix_id__approve_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/{fix_id}/faq-edit":{"post":{"tags":["fixes"],"summary":"Faq Edit Fix","description":"\"Ret først\" på et ventende FAQ-schema-fix: kundens rettede par.\n\nBody: {\"pairs\": [{\"index\": int, \"q\": str, \"a\": str}, ...]}\n`index` peger på parret i det fix, kortet viser. Udeladte par falder ud\naf markeringen; nye par afvises (de står ikke på siden — Googles regel),\nog hver tekst må kun være en let rettelse (stavefejl, formulering) af\ndet, der står på siden — se faq_edit_problems.\n\nSkriver et NYT pending fix via insert_prepared_fix (samme checksum-guard\nog supersede som alle andre skriveveje) og returnerer det. Godkendelsen\ner et separat kald til /approve, som for llms.txt og meta. Uændrede par →\nden eksisterende række genbruges (`reused: true`).\n\nAuth: som approve — HERMES eller kundens Supabase-JWT.","operationId":"faq_edit_fix_api_customers__customer_id__fixes__fix_id__faq_edit_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/{fix_id}/reject":{"post":{"tags":["fixes"],"summary":"Reject Fix","description":"Afvis et fix — fjernes fra feedet (hvis det var godkendt).\n\nAuth: HERMES-nøgle (backend) ELLER den indloggede kundes Supabase-JWT\n(Authorization: Bearer) hvis brugeren ejer kunden — se _authorize_dashboard_write.","operationId":"reject_fix_api_customers__customer_id__fixes__fix_id__reject_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/{fix_id}/revert":{"post":{"tags":["fixes"],"summary":"Revert Fix","description":"Fortryd et anvendt fix — det forlader feedet, og klienten holder op med\nat udsende det ved naeste synk.\n\nUden fortryd giver ingen fuldmagt, og uden fuldmagt er der ingen\nmedarbejder. Derfor er den her ikke en bekvemmelighed, men forudsaetningen\nfor hele §0-visionen.\n\nSvaret siger AERLIGT, om sitet faktisk ruller tilbage: for typer der\nrenderes fra feed-cachen (llms.txt, schema, meta, bot-policy) gor det.\nFor typer der har skrevet i kundens eget indhold — interne links — fjerner\nfortryd kun fixet fra feedet; teksten i indlaegget bliver staaende, og det\nskal UI'et sige, ikke skjule.","operationId":"revert_fix_api_customers__customer_id__fixes__fix_id__revert_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-autonomy":{"get":{"tags":["fixes"],"summary":"Get Fix Autonomy","description":"Hele fuldmagten, som opsaetnings-fladen viser den.","operationId":"get_fix_autonomy_api_customers__customer_id__fix_autonomy_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["fixes"],"summary":"Set Fix Autonomy","description":"Saet fuldmagten for en fix-type. Body: {fix_type, mode}.\n\nAuth som resten af dashboard-skrivningerne: HERMES-noglen eller kundens\neget Supabase-JWT.","operationId":"set_fix_autonomy_api_customers__customer_id__fix_autonomy_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/agent-run":{"get":{"tags":["fixes"],"summary":"Latest Agent Run","description":"Seneste natkorsel. Svarer altid — ogsaa naar der ingen er.","operationId":"latest_agent_run_api_customers__customer_id__agent_run_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-effects":{"get":{"tags":["fixes"],"summary":"List Fix Effects","description":"Målte effekter (+14/+28) pr. anvendt fix — dashboardets effekt-piller.\n\nKun målte fakta (fix_effects skrives aldrig med opfundne nuller), saa\nen tom liste betyder \"intet modent endnu\", ikke \"ingen effekt\".","operationId":"list_fix_effects_api_customers__customer_id__fix_effects_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/health/employee":{"get":{"tags":["fixes"],"summary":"Employee Health","description":"Natkørsels-alarmen (fase C): stilhed er selv en fejl.\n\nEn kunde med fuldmagt (mindst én type på auto) SKAL have en agent_runs-\nkvittering inden for 26 timer — ellers er medarbejderen tavst død, og\ndet er præcis den tilstand statuslinje-designet forbyder. Endpointet er\nOFFENTLIGT og deler kun aggregater (antal, aldrig kunde-id'er): det\nkaldes af en scheduled CI-kørsel, som fejler rødt og dermed mailer\nMartin, hvis her svares 503. Overvågningen bor UDEN FOR pipelinen med\nvilje — den proces der skal overvåges, kan ikke være sin egen vagt.\n\nKontrakten med pipelinen (rettet 2026-08-31): 26-timers-vinduet er ikke\n\"blev kunden scannet\", men \"nåede nattens tick frem til kunden\". Springer\npipelinen bevidst en kunde over — ikke i tur, lukket i registret, free\nuden badge — skriver den en kvittering med blocked_reason i stedet\n(pipeline._record_skip_receipt). Derfor må vinduet blive stramt: udebliver\nticket, eller vælter kundens kørsel undervejs, findes ingen række af nogen\nslags, og her svares stadig 503.","operationId":"employee_health_api_health_employee_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/fixes/persist":{"post":{"tags":["fixes"],"summary":"Request Persist","description":"Bed klienten skrive alle aktive fixes permanent ind i kundens eget site.\n\nUden det her er loftet \"opsiger du, beholder du forbedringerne\" ikke sandt:\npluginnet renderer fixes i wp_head fra sin feed-cache, saa holder\nsynkroniseringen op, forsvinder de — strukturelt samme situation som Alli\nAI's overlay, som vi kritiserer.\n\nNaar en raekke findes, saetter feedet mode=\"persist\". Klienten skriver saa\nllms.txt som en rigtig fil, title/description ind i sitets eget SEO-plugin\nog JSON-LD i sin egen permanente lagring, og korer derefter videre UDEN os.","operationId":"request_persist_api_customers__customer_id__fixes_persist_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fixes/report-applied":{"post":{"tags":["fixes"],"summary":"Report Applied Fixes","description":"Rapportér fixes en EKSTERN klient allerede har anvendt selv.\n\nBruges af Shopify-appen (seo-proof), der skriver GEO-fixes direkte i butikken\nvia metafields/Admin-mutations. I modsætning til WP-flowet (prepare→approve→\nfeed) er disse fixes anvendt OG live i det øjeblik de rapporteres, så de gemmes\nmed status='approved', source='shopify' og last_reported_live_at=nu — og dukker\nop i Dashboardets track-timeline side om side med WP-fixes.\n\nAuth: HERMES (Depends(_optional_hermes)) — seo-proof er vores egen trusted\nserver (holder HERMES server-side), ikke untrusted merchant-kode som WP-\npluginet (der bruger X-Client-Key). Scoping er på customer_id (1:1 med\nshop-domænet), så ingen krydskunde-adgang.\n\nBody:\n    {\n      \"source\": \"shopify\",              # provenance (default 'shopify')\n      \"fixes\": [\n        {\"fix_type\": \"schema_jsonld\", \"subtype\": \"organization\",\n         \"url_pattern\": \"/\", \"title\": \"...\", \"content\": \"<jsonld>\",\n         \"count\": 1, \"detail\": {...}},\n        ...\n      ]\n    }\nfix_type SKAL være i VALID_FIX_TYPES; finere granularitet via `subtype`\n(kanonisk vokabular i FIX_SUBTYPE_LABELS). `count`/`detail` er valgfri og\nlægges i payload (fx aggregeret \"SEO anvendt på 42 produkter\").","operationId":"report_applied_fixes_api_customers__customer_id__fixes_report_applied_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feed/fixes":{"get":{"tags":["fixes"],"summary":"Get Fixes Feed","description":"Versioneret feed af kundens GODKENDTE fixes. Auth: X-Client-Key.\n\nUnderstøtter If-None-Match → 304 (klienten synker hver time; uændret feed\nkoster ingen båndbredde). ETag sendes altid.","operationId":"get_fixes_feed_api_feed_fixes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/feed/status":{"post":{"tags":["fixes"],"summary":"Report Feed Status","description":"Klienten pinger hjem: plugin-version + aktive fixes. Auth: X-Client-Key.\n\nBody: {plugin_version?, platform_version?, php_version?, feed_etag?,\n       active_fix_ids?: [uuid], details?: {...}}\nGemmes som seneste status pr. installation; aktive fixes får\nlast_reported_live_at (→ \"implementeret\" i dashboard + delta-email).","operationId":"report_feed_status_api_feed_status_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/wordpress/plugin":{"get":{"tags":["fixes"],"summary":"Download Wordpress Plugin","description":"Omdiriger til WP.org-plugin'ets nyeste stabile zip (302).","operationId":"download_wordpress_plugin_api_wordpress_plugin_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/accumulated":{"get":{"tags":["fixes"],"summary":"Customer Accumulated","description":"P1: hvad kunden har opbygget — skifteomkostningen, navngivet ærligt.\n\nSvarform (additiv kontrakt):\n  { live_fixes, approved_fixes, backlog_done, ai_answers,\n    history: {since, months}, entity_verified, proofs_available,\n    cancellation: {da, nb, sv, en} }","operationId":"customer_accumulated_api_customers__customer_id__accumulated_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue":{"get":{"summary":"Get Fix Queue","description":"De 5 serverede fixes + tællere + forbrugsmåler. Additivt, version:1.\n\n`summary=1` svarer KUN tællerne — samme rækker, samme `servable()`-filter,\nmen uden oversættelse, forbrugsmåler og uden genopfyldning.\n\nHvorfor tilstanden findes: site-listen på /app skal kunne vise HVOR MEGET\nder venter pr. site, og det tal skal være det samme, som work-siden viser\nindenfor. Uden en billig tilstand ville listen enten tælle noget andet\n(fejlen 2026-09-16: \"0 Fixes venter\" over fem ventende kort) eller udløse\nen genopfyldning pr. site, hver gang nogen åbner en oversigt. En\nlistevisning må aldrig skrive i kundens kø.","operationId":"get_fix_queue_api_customers__customer_id__fix_queue_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}},{"name":"summary","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Summary"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/work-plan":{"get":{"summary":"Get Work Plan","description":"Skrivebordets blok 5: hvad medarbejderen tager fat i som det næste.\n\nToppen af backloggen (status=backlog, prioriteret) — de emner næste\nkørsel vil forsøge at servere/materialisere — med en ærlig bane pr. emne\n(self/draft/you, se plan_lane) og kørselskadencen, så frontenden kan\nsige \"i nat\" (daglig) eller \"inden for en uge\" (ugentlig) uden at\nopfinde ugedage, vi ikke kender.","operationId":"get_work_plan_api_customers__customer_id__work_plan_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/work-log":{"get":{"summary":"Get Work Log","description":"AI-medarbejderens arbejdslog (etape 2, additiv, version:1).\n\nSamme tre ærlige spande som ugerapporten — live (fixes VI pushede,\nklient-bekræftet), solved (auto-verificeret løst, neutral kredit), you\n(kundens claims; 'verified' kun ved målt verifikation) — så dashboardet\nkan vise \"Det klarede jeg\"-kortet. Serverede opgaver ligger allerede i\n/fix-queue og duplikeres ikke her.\nSpec: docs/SPEC-ai-medarbejder-etape2-persona-2026-08-05.md.","operationId":"get_work_log_api_customers__customer_id__work_log_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/refresh":{"post":{"summary":"Post Fix Queue Refresh","description":"HERMES: kør producers + verify + refill nu (bruges af backfill/admin).","operationId":"post_fix_queue_refresh_api_customers__customer_id__fix_queue_refresh_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"include_network","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Network"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/{fix_id}/dismiss":{"post":{"summary":"Post Fix Dismiss","description":"»Vis ikke igen« — persisteres med grund og opsamles i admin-aggregatet.","operationId":"post_fix_dismiss_api_customers__customer_id__fix_queue__fix_id__dismiss_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/{fix_id}/claim-done":{"post":{"summary":"Post Fix Claim Done","description":"»Jeg har gjort det« — synkron verifikation hvor muligt; ellers afventer\nmålingen nattens sweep. done sættes KUN målt (undtagen manual_claim).\n\nValgfri body {\"url\": \"...\"}: profil-fixes (entity.*) kan aflevere linket\ntil den nye profil — det gemmes i entity_setup.same_as (kilden til sandhed\nfor JSON-LD sameAs + llms.txt's 'Officielle profiler'), llms.txt\nregenereres i baggrunden, og verifikationen kan derefter MÅLE fixet —\nogså for platforme der ellers var manual_claim.","operationId":"post_fix_claim_done_api_customers__customer_id__fix_queue__fix_id__claim_done_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/{fix_id}/generate":{"post":{"summary":"Post Fix Generate","description":"»Fix«-knappen: ai_prompt-leverancer får en gratis deterministisk prompt;\nai_text/external_action får AI-genereret tekst (betalt flade, månedsloft).\nCache: genereret tekst gemmes og genbruges til data ændrer sig.\n\n`?regenerate=1` springer cachen over og skriver en ny tekst (tæller med i\nmånedsloftet). Nødvendigt når prompten/guidelinen er rettet — ellers\nserveres den gamle tekst for evigt (Wikidata-formen, 2026-08-06).","operationId":"post_fix_generate_api_customers__customer_id__fix_queue__fix_id__generate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}},{"name":"regenerate","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Regenerate"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/{fix_id}/materialize-meta":{"post":{"summary":"Post Materialize Meta","description":"Gør et meta-backlog-item til et leverbart `meta`-fix (pending).\n\nBetalt flade (Martin 2026-08-10): meta koster et AI-kald pr. side, i\nmodsætning til llms.txt og schema. Loft og statuskoder er DE SAMME som\n/generate, så frontenden kan genbruge sin paywall-visning:\n  402 ai_generation_requires_paid_plan  — free-plan (loft 0)\n  429 ai_generation_limit_reached       — månedsloftet brugt\n\nResultatet skrives som `source='auto_fix'`, altså gennem den normale\nprepare→approve→feed-vej: kunden godkender selv (i geoa.app-dashboardet\neller i sit eget admin via Shopify-appens \"Geoa recommends\"), og først\nderefter indgår det i feedet.","operationId":"post_materialize_meta_api_customers__customer_id__fix_queue__fix_id__materialize_meta_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}},{"name":"regenerate","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Regenerate"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/fix-queue/{fix_id}/materialize-content-patch":{"post":{"summary":"Post Materialize Content Patch","description":"Gør et H1-backlog-item til et leverbart `content_patch`-fix (pending).\n\nSpejler materialize-meta: samme auth, samme prepare→approve→feed-vej.\nMoney-laget gælder KUN set_h1 (koster et AI-kald og cachelagres i\nai_text); demote_extra_h1 er deterministisk og gratis. content_patch er\nWRITES_CUSTOMER_CONTENT — kunden godkender ALTID selv (aldrig auto).","operationId":"post_materialize_content_patch_api_customers__customer_id__fix_queue__fix_id__materialize_content_patch_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"fix_id","in":"path","required":true,"schema":{"type":"string","title":"Fix Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/fix-dismissal-stats":{"get":{"summary":"Get Fix Dismissal Stats","description":"Aggregat pr. kind: dismissal-rate, seen_before-andel (gen-udsendelses-\nbug-signal) og serve_count. Flagger kinds med rate > 40% el. serve_count > 2.","operationId":"get_fix_dismissal_stats_api_admin_fix_dismissal_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/fix-stage-reach":{"get":{"summary":"Get Fix Stage Reach","description":"Rækkevidden af trin-holdet, som backloggen ligger NU — pr. kunde.\n\nMonitor-beviset bag `FIXQ_STAGE_ENFORCE`. Log-linjen\n`[fixq] MONITOR stage-hold` skrives kun under nat-refreshet, så den kan\nførst sige noget natten efter et deploy — og et tomt log-vindue betyder\ndér \"har ikke haft chancen\", ikke \"rammer ingen\". Den her rute læser\nrækkerne direkte og svarer med det samme, uden at ændre noget.\n\nLæs svaret sådan: `blockers` er de åbne trin-1-3-kort, der ville holde,\n`held` er de linkkort, der ville blive holdt, og `blockers_self_serve`\nsiger, hvor mange af blokkerne kunden faktisk selv kan lukke. En kunde\nmed `held > 0` og `blockers_self_serve == 0` er fejlskuddet at kigge\nefter: hun ville se sine katalog-kort synke bag arbejde, hun ikke kan\nkomme videre med selv.","operationId":"get_fix_stage_reach_api_admin_fix_stage_reach_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/backfill-fix-backlog":{"post":{"summary":"Post Backfill Fix Backlog","description":"Engangs-backfill: refresh + sweep + refill for alle aktive kunder.","operationId":"post_backfill_fix_backlog_api_admin_backfill_fix_backlog_post","parameters":[{"name":"include_network","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Network"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/topic-graph":{"get":{"summary":"Get Topic Graph","description":"Emnekortet for kundens site. Auth: HERMES (samme værn som de øvrige\ndashboard-læsninger, fail-open uden nøgle i dev).","operationId":"get_topic_graph_api_customers__customer_id__topic_graph_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Topic Graph Api Customers  Customer Id  Topic Graph Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/content-links":{"post":{"summary":"Report Content Links","description":"Klienten melder artiklernes links hjem (X-Client-Key — ALDRIG HERMES).\n\nBody: {links: [{url, post_url, post_title?, anchor?}]} — max 100 pr.\nkald. AFSTEMNING pr. artikel: for hver post_url i payloaden erstattes\nrækkerne (et link kunden har fjernet, forsvinder — ellers ville et\n\"brudt link\"-fix aldrig kunne lukkes ved at fjerne linket). Målingen\n(broken/ok) sker i nattens job, aldrig her. Allerede målte links\nbeholder deres status via upsert (merge-duplicates rører ikke status).","operationId":"report_content_links_api_apps_content_links_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/content-links":{"get":{"summary":"Content Links Report","description":"Brudte links-rapporten (HERMES) — pr. artikel, med målt status.","operationId":"content_links_report_api_customers__customer_id__content_links_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/apps/media-alt":{"post":{"summary":"Generate Media Alt","description":"Skriv alt-tekster for op til 10 billeder (X-Client-Key).\n\nBody: {images: [{media_id, url, filename?, post_title?}]}. Betalt flade:\nfree-plan får 402 med pakke-sprog (samme klasse som al AI-tekst — aldrig\nen monitor-fase, gaten har været hård fra dag 1 i fix-maskinen).\nTeksterne skrives fra de FAKTISKE billeder på kundens sprog; pluginet\nviser dem i wp-admin, og merchantens klik dér er godkendelsen.","operationId":"generate_media_alt_api_apps_media_alt_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/media-alt/applied":{"post":{"summary":"Report Media Alt Applied","description":"Pluginet melder anvendte alt-tekster (merchanten godkendte i wp-admin).\n\nSkrives som ét approved meta/image_alt-fix (source='wordpress_media' —\ntæller derfor ALDRIG i free-tier-gatens AI Foundation) til dashboardets\ntimeline. Body: {count, media_ids?: []}.","operationId":"report_media_alt_applied_api_apps_media_alt_applied_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/r/d/{customer_id}":{"get":{"summary":"Digest Click Redirect","description":"Log klikket (hvis tokenet er gyldigt) og send ALTID kunden videre.\n`fix` (valgfrit UUID) føres videre som query-param på dashboardet, så\nfrontenden kan åbne det konkrete kort — ukendte params er harmløse.","operationId":"digest_click_redirect_r_d__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"k","in":"query","required":false,"schema":{"type":"string","default":"","title":"K"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}},{"name":"fix","in":"query","required":false,"schema":{"type":"string","default":"","title":"Fix"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/digest-telemetry":{"get":{"summary":"Digest Telemetry","description":"Samlet telemetri-billede til den ugentlige log-fase-gennemgang.","operationId":"digest_telemetry_admin_digest_telemetry_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/review-kit/personalize":{"post":{"summary":"Post Review Kit Personalize","description":"Den personlige anmeldelses-besked (2026-09-06): personlig om KØBET,\naldrig ros eller salg. Betalte planer får AI-beskeden (gatet, skabelon som\nsikkerhedsnet); free får skabelonen — samme pakke, samme links, samme\nregler. Ingen persondata: input der ligner e-mail/telefon/URL → 400.","operationId":"post_review_kit_personalize_api_customers__customer_id__review_kit_personalize_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/app/review-kit":{"post":{"summary":"Post App Review Kit","description":"Anmeldelses-pakken til distribuerede klienter (X-Client-Key, aldrig\nHERMES). Samme pakke som dashboardet; med `purchase` også den personlige\nbesked (AI på betalte planer, gatet; ellers skabelonen). Klienten\nerstatter selv [fornavn]/[dato] og sender fra sit eget system.","operationId":"post_app_review_kit_api_app_review_kit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppReviewKitRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/review-replies/stored":{"get":{"summary":"Get Stored Reviews","description":"De gemte Google-anmeldelser (review_intelligence) med tekst — det,\ndashboardet viser med »skriv udkast«-knappen (spor C, 2026-09-06). Gratis\nog læse-kun; udkastet er stadig POST /draft (betalt flade).","operationId":"get_stored_reviews_api_customers__customer_id__review_replies_stored_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/review-replies/draft":{"post":{"summary":"Post Review Reply Drafts","description":"Udkast til svar på de indsendte anmeldelser. Kunden poster SELV på\nGoogle — der er ingen auto-post og ingen Google-skriveadgang (bevidst).","operationId":"post_review_reply_drafts_api_customers__customer_id__review_replies_draft_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gbp/auth-url":{"get":{"summary":"Gbp Auth Url","description":"Googles login-URL for »Forbind din Google-profil«. Frontenden sender\nbrowseren derhen; callbacken fører kunden tilbage med ?gbp=ok|choose|error.","operationId":"gbp_auth_url_gbp_auth_url_get","parameters":[{"name":"customer_id","in":"query","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gbp/callback":{"get":{"summary":"Gbp Callback","operationId":"gbp_callback_gbp_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","default":"","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp":{"get":{"summary":"Gbp Status","description":"Forbindelsens tilstand til dashboard-kortet.","operationId":"gbp_status_api_customers__customer_id__gbp_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Gbp Disconnect","description":"Frakobl: træk grantet hos Google (ellers lever adgangen videre, uden at\nnogen kan se den) og slet vores kopi. Best-effort mod Google.","operationId":"gbp_disconnect_api_customers__customer_id__gbp_delete","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/locations":{"get":{"summary":"Gbp Locations","operationId":"gbp_locations_api_customers__customer_id__gbp_locations_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/location":{"post":{"summary":"Gbp Select Location","description":"Kunden vælger profil. Valget afgøres mod Googles egen liste for den\nforbundne konto — en profil kontoen ikke kan administrere afvises.","operationId":"gbp_select_location_api_customers__customer_id__gbp_location_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationChoice"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/reviews":{"get":{"summary":"Gbp Reviews","description":"Profilens anmeldelser, hentet live (ingen cache — jf. politikken).\nUbesvarede først; tællerne udledes af den liste, der returneres.","operationId":"gbp_reviews_api_customers__customer_id__gbp_reviews_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/reviews/draft":{"post":{"summary":"Gbp Review Drafts","description":"Udkast til svar på ubesvarede anmeldelser (samme motor og samme gate\nsom /review-replies/draft). Intet udgives her — det er næste kald.","operationId":"gbp_review_drafts_api_customers__customer_id__gbp_reviews_draft_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/reviews/{review_id}/reply":{"post":{"summary":"Gbp Publish Reply","description":"Udgiv ÉT svar, som kunden har godkendt. Anmeldelsen hentes live fra\nGoogle (aldrig fra klienten), teksten går gennem output-gatet, og svaret\ngenlæses hos Google, før vi melder »udgivet«.","operationId":"gbp_publish_reply_api_customers__customer_id__gbp_reviews__review_id__reply_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"review_id","in":"path","required":true,"schema":{"type":"string","title":"Review Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/gbp_connect__ReplyIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/gbp/posts":{"post":{"summary":"Gbp Publish Post","description":"Udgiv ÉT opslag på profilen, som kunden har godkendt.","operationId":"gbp_publish_post_api_customers__customer_id__gbp_posts_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/review-intel":{"get":{"summary":"Get Review Intel","description":"Seneste Review Intelligence-snapshot for dashboardet.\n\nstatus: 'none' (aldrig målt endnu) | 'pending' (tasks undervejs) |\n'done'. customer.measured=false betyder \"profilen kunne ikke måles\"\n(ingen offentlig GBP / forkert-virksomhed-guard / AI-fejl) — det er\nIKKE det samme som nul temaer.","operationId":"get_review_intel_api_customers__customer_id__review_intel_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-briefs":{"get":{"tags":["content-studio"],"summary":"List Briefs","description":"Kundens gemte briefs. ARKIVEREDE er som udgangspunkt UDE.\n\n\"Afvist\" skal betyde afvist (2026-09-20). Statussen `archived` har\neksisteret siden migration 049, men ingen flade satte den nogensinde —\net genereret brief kunne derfor hverken afvises eller fjernes, og hos\nPartnerDecode stod et udkast om parterapi, som kundens egen opsætning\nhavde forladt tre dage før. Listen skjuler nu det arkiverede; hele\nsættet kan stadig hentes med `?status=archived`.","operationId":"list_briefs_api_customers__customer_id__content_briefs_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["content-studio"],"summary":"Create Brief","operationId":"create_brief_api_customers__customer_id__content_briefs_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BriefCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-briefs/{brief_id}":{"patch":{"tags":["content-studio"],"summary":"Update Brief","operationId":"update_brief_api_customers__customer_id__content_briefs__brief_id__patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"brief_id","in":"path","required":true,"schema":{"type":"string","title":"Brief Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BriefUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/brand-memory":{"get":{"tags":["content-studio"],"summary":"Get Brand Memory","operationId":"get_brand_memory_api_customers__customer_id__brand_memory_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["content-studio"],"summary":"Update Brand Memory","operationId":"update_brand_memory_api_customers__customer_id__brand_memory_patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandMemoryUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/brand-memory/suggest":{"post":{"tags":["content-studio"],"summary":"Suggest Brand Memory","description":"Udkast til Brand Brain fra alle tre kilder vi ejer — plus spørgsmål.\n\nKilder (2026-08-23, SWOT-anbefaling #1): sitets egen tekst, kundens\nregisterfakta (Nordic Data) og de AI-svar vi allerede har målt. `sources`\ni svaret siger pr. felt hvor udkastet kom fra. `facts` og `prices` udkastes\nKUN deterministisk (registerfakta hhv. ordrette prislinjer fra sitet) —\naldrig fra modellen.\n\nSkriver INTET. Returnerer forslag, som frontenden viser som udkast, og som\nmennesket gemmer via den eksisterende PATCH. Så kan et forkert udkast aldrig\nnå at forurene genereringen uden at nogen har set det.","operationId":"suggest_brand_memory_api_customers__customer_id__brand_memory_suggest_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-overlap":{"get":{"tags":["content-studio"],"summary":"Content Overlap","description":"Har kunden allerede skrevet om det her?\n\nKannibaliserings-analyse findes allerede BAGEFTER (GSC-baseret, i\nmulighedsmotoren). Det her er tjekket FØR: emnet dublerede to udgivne\nindlæg på geoa, og var det gået live, ville tre sider have konkurreret om\nsamme søgning.\n\nAdvarer, blokerer ikke — en opdatering eller en ny vinkel er et legitimt\nvalg. Men det skal træffes, før kvoten er brugt.","operationId":"content_overlap_api_customers__customer_id__content_overlap_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"topic","in":"query","required":false,"schema":{"type":"string","default":"","title":"Topic"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-opportunities":{"get":{"tags":["content-studio"],"summary":"Content Opportunities","description":"Ranket liste af content-muligheder fra alle diagnose-kilder.\n\n`lang` (da/no/sv/en): begrundelsen skrives af backenden på dansk og\noversættes her til FLADENS sprog. Gennemgang 13/9 (Martins Chrome,\ncopywriterai.se): \"AI'en nævner jer ikke, når nogen spørger …\" stod på\ndansk midt i et svensk studio. Uden `lang` (ældre klienter) = dansk.","operationId":"content_opportunities_api_customers__customer_id__content_opportunities_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":12,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content":{"get":{"tags":["content-studio"],"summary":"List Content","operationId":"list_content_api_customers__customer_id__studio_content_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"","title":"Status"}},{"name":"format","in":"query","required":false,"schema":{"type":"string","default":"","title":"Format"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["content-studio"],"summary":"Create Content","operationId":"create_content_api_customers__customer_id__studio_content_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/brand-audit":{"get":{"tags":["content-studio"],"summary":"Brand Audit","description":"Hvilke af kundens ARTIKLER sælger ikke for kunden? (G-055)\n\nFindes, fordi det ikke er nok at rette generatoren. De gamle artikler\nligger stadig i kontoen, kan stadig markeres Klar og importeres — fire af\nCheckBiz' fem artikler var fra den dato, hvor fejlen blev skrevet. Uden\ndenne rute ville ejeren skulle åbne hver enkelt og læse den til bunds,\nhvilket er præcis dét, \"on autopilot\" lover, at han slipper for.\n\nMåles på den AKTUELLE tekst, aldrig på et gemt meta-felt: en artikel, der\ner redigeret siden, skal bedømmes på det, der står nu.","operationId":"brand_audit_api_customers__customer_id__studio_content_brand_audit_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/{content_id}":{"patch":{"tags":["content-studio"],"summary":"Update Content","operationId":"update_content_api_customers__customer_id__studio_content__content_id__patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/{content_id}/regenerate-meta":{"post":{"tags":["content-studio"],"summary":"Regenerate Meta","description":"Genskab meta description + FAQ ud fra artiklens NUVÆRENDE tekst.\n\nHvorfor (dogfood checkbiz.eu 2026-09-16): sidepanelets META DESCRIPTION\nog FAQ blev skrevet ved genereringen og fulgte aldrig med, når teksten\nblev skrevet om. Coachens `schema_congruence` fejlede, og kunden havde\ningen vej til grøn. Et gem udleder nu selv FAQ'en af teksten\n(faq_following_body); denne rute er knappen, der også skriver en ny\nmeta description og henter FAQ'en med AI, når teksten ikke har en\nFAQ-sektion at læse.\n\nSchemaet følger med (Article.description + FAQPage), og Coach regnes om.\nFail-open på AI: fejler meta-kaldet, beholdes den gamle tekst, og svaret\nsiger det (`meta_description_regenerated: false`).","operationId":"regenerate_meta_api_customers__customer_id__studio_content__content_id__regenerate_meta_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/{content_id}/deliver-section":{"post":{"tags":["content-studio"],"summary":"Deliver Section","description":"Læg købsguide-blokken på den EKSISTERENDE kategori-/serviceside.\n\nPlaybookens punkt 6, leverance-halvdelen. Guiden er skrevet som\n`page_type=service_guide` og hører hjemme UNDER produktgitteret på den\nside, fix-kortet `onpage.service_page.guide_missing` pegede på — ikke som\net nyt blogindlæg. Derfor:\n\n  * artikelfeedet filtrerer sidetypen fra (PAGE_TYPES_NOT_IN_ARTICLE_FEED),\n  * og her leveres den som `content_patch` op=`append_section` på sidens\n    egen sti, gennem fix-maskinens sædvanlige vej: insert_prepared_fix →\n    kunden godkender i dashboardet → pluginet placerer den.\n\nPå en WordPress-SIDE skrives blokken ind i sidens eget indhold (kunden\nkan redigere den bagefter). På en WooCommerce-KATEGORI — som er et\ntaxonomy-term uden post_content — renderer pluginet den under\nproduktgitteret og over footeren, og afvisning i dashboardet fjerner den\nigen. Begge veje er 0.9.43+.\n\nKun kundens eget domæne, kun en kanal der faktisk kan udføre\ncontent_patch på stien, og aldrig en tom blok.","operationId":"deliver_section_api_customers__customer_id__studio_content__content_id__deliver_section_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SectionDeliver"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/{content_id}/review":{"post":{"tags":["content-studio"],"summary":"Confirm Review","description":"Registrér en bekræftet redaktionel gennemgang (AI Act art. 50(4)).\n\nEr sin egen handling, fordi den skal være det. Statusskift, gem og\nudgivelse er proceduremæssige og tæller ikke — se noten ved\nREVIEW_ATTESTATION.\n\nSvaret bærer erklæringen tilbage, så fladen kan vise NØJAGTIG den tekst,\nbrugeren har stået inde for, frem for sin egen omskrivning.","operationId":"confirm_review_api_customers__customer_id__studio_content__content_id__review_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewConfirm"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-usage":{"get":{"tags":["content-studio"],"summary":"Studio Usage","description":"Forbrugsmåler: artikler OG briefs brugt/tilbage denne måned + plan.\n\nBrief-felterne er ADDITIVE og ligger kun her (ikke i `_studio_usage`),\nfordi `_studio_usage` er indlejret som `usage` i et dusin genererings-svar\n— dér ville de koste et ekstra tælle-opslag pr. artikel uden at blive læst.","operationId":"studio_usage_api_customers__customer_id__studio_usage_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-briefs/quota-check":{"post":{"tags":["content-studio"],"summary":"Brief Quota Check","description":"Reservér én brief-generering FØR LLM-kaldet.\n\nHvorfor her og ikke i scribe: kvoten bor i backenden, som er den samme,\nder håndhæver artikel-loftet — og en klient, der tæller sig selv, tæller\nkun så længe den vil. Scribes `generateBrief` kalder denne rute som sit\nFØRSTE skridt; svarer den 402, når kaldet til Lovable AI Gateway aldrig\nat koste noget.\n\nHvorfor ikke tælle på `POST /content-briefs`: dét endpoint rammes først\nEFTER genereringen er betalt, og kun hvis kunden vælger at gemme. Det ville\nmåle alt andet end det, der koster.","operationId":"brief_quota_check_api_customers__customer_id__content_briefs_quota_check_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/BriefQuotaCheck"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/page-types":{"get":{"tags":["content-studio"],"summary":"List Page Types","description":"Bølge 2 (2026-08-14): sidetype-skabeloner for kunden, arketype-gatet.\n\nAdditivt — frontendens skabelonvælger læser listen her, så gatingen bor\nét sted (backend), aldrig i frontenden. Kontrakt:\ndocs/overlevering-lovable-content-sidetyper-2026-08-14.md.","operationId":"list_page_types_api_customers__customer_id__page_types_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/generate":{"post":{"tags":["content-studio"],"summary":"Generate Article","description":"Brief → komplet artikel (Brand Brain-injiceret), persisteret som draft.","operationId":"generate_article_api_customers__customer_id__articles_generate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/city-series":{"post":{"tags":["content-studio"],"summary":"Generate City Series","description":"By-serie (2026-08-23): ét brief → N by-sider, hver beriget med ÆGTE\nlokale tal (city_facts: kommune/landsdel + Eurostat-befolkning via Nordic\nData). En by UDEN målte lokale fakta springes over med begrundelse — en\nside, der kun har byttet bynavnet ud, er den tynde side, vi selv noindexer.\n\nHver side er en almindelig studio-artikel (page_type=city_page, kvote pr.\nside, Port, Coach, feed). Efter generering måles parvis lighed; par over\n0,8 flages i meta.series.too_similar_to, så UI'et kan vise det FØR udgivelse.","operationId":"generate_city_series_api_customers__customer_id__articles_city_series_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CitySeriesGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/data-report":{"post":{"tags":["content-studio"],"summary":"Generate Data Report","description":"Mikro-rapport fra kundens EGNE målte tal (2026-08-23).\n\nOriginale data er den kildetype AI-assistenter citerer frem for genbrugt\ntekst. Rapporten er en almindelig studio-artikel (format=article,\npage_type=data_report) — tæller i kvoten, går gennem Porten, Coach, feed\nog publicering som alt andet. Brief'et syntetiseres her (source\ndata_report), så kunden ikke skal skrive et brief for at få sine egne tal\nud. Tallene: se `_measurement_facts` — ingen AI, kun DB.","operationId":"generate_data_report_api_customers__customer_id__articles_data_report_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataReportGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/refine":{"post":{"tags":["content-studio"],"summary":"Refine Article","description":"Forbedr et udkast: shorten/lengthen/concretize (LLM), humanize\n(deterministisk humanizer), eller custom-instruks.","operationId":"refine_article_api_customers__customer_id__articles__content_id__refine_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleRefine"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/localize":{"post":{"tags":["content-studio"],"summary":"Localize Article","description":"Regenerér en artikel på et andet nordisk sprog — som ny artikel.","operationId":"localize_article_api_customers__customer_id__articles__content_id__localize_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleLocalize"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/coach":{"get":{"tags":["content-studio"],"summary":"Article Coach","description":"Content Coach for et gemt udkast (deterministisk, altid frisk).\n\n── SAMME ARTIKEL MÅTTE IKKE KUNNE GIVE TO TAL (2026-09-16) ─────────────\n\nMålt som kunde på CheckBiz 16/9, på ét og samme øjeblik:\n    UI:  score 72 · \"23 af 32 trust-signaler på plads\"\n    API: score 73 · passed 22 · total 30\nBegge var internt konsistente (23/32 → 72, 22/30 → 73) — de scorede bare\nto FORSKELLIGE lister. UI'et viste `studio_content.trust_score`, som er\net øjebliksbillede gemt ved genereringen, hvor coachen fik både briefet\nog titlen med. Denne rute gav hverken det ene eller det andet, så de to\nbrief-afhængige tjek (`h1_head_term`, `title_promise`) faldt ud, og\n`title_promise` kunne slet ikke stilles uden en titel at måle.\n\nKunden kan ikke se, at der er to lister — kun at tælleren, nævneren og\nscoren alle tre er uenige om den samme artikel. Ruten henter derfor nu\nde samme tre ting, genereringen havde: titlen, briefet og briefets\n`source`. Den LEVENDE beregning er den autoritative; `trust_score` er\nhistorik.\n\nSproget følger artiklen, når klienten ikke selv siger noget. `lang=\"\"`\nbetyder i `coach_strings` \"intern kalder → dansk\", og det er rigtigt for\nde interne kaldere — men her sidder et menneske og læser råd om en\nENGELSK artikel. Rækken kender sit eget sprog; en klient, der sender\n`?lang=`, vinder stadig over den.","operationId":"article_coach_api_customers__customer_id__articles__content_id__coach_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/facts":{"get":{"tags":["content-studio"],"summary":"Own Company Facts","description":"Kundens egne verificerede registerfakta — dem artiklerne grounder på.\n\nViser præcis hvad vi ville give modellen at skrive ud fra, med kilde og\ndato pr. felt. Er der intet CVR/orgnr på kunden, siger vi det frem for at\nlade feltet stå tomt uden forklaring.","operationId":"own_company_facts_api_customers__customer_id__facts_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/facts/lookup":{"post":{"tags":["content-studio"],"summary":"Lookup Facts","description":"Slå en VILKÅRLIG virksomhed op (kunde, partner, case-virksomhed).\n\nReklamebeskyttelse håndhæves her: har virksomheden frabedt sig\nmarkedsføringsbrug af sine registerdata, får kalderen `blocked: true` og\ningen fakta. Det er ikke en teknisk begrænsning — det er registrets eget\nsignal om, at de ikke vil bruges sådan.","operationId":"lookup_facts_api_customers__customer_id__facts_lookup_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactLookup"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/facts/connect":{"post":{"tags":["content-studio"],"summary":"Connect Company","description":"Kobl kunden til sit CVR/orgnr, så artikler kan grounde på registret.\n\nUden nummeret findes der ingen fakta at grounde på — og nummeret kan ikke\ngættes sikkert fra domænet: navnesøgning rammer den forkerte virksomhed\noftere end man tror. Derfor bekræfter mennesket ét opslag ÉN gang, og så\nligger det fast.\n\nSkriver registerkolonnerne på `customers` (migration-003), så resten af\nplatformen — ikke kun content — får glæde af dem.","operationId":"connect_company_api_customers__customer_id__facts_connect_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactLookup"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/versions":{"get":{"tags":["content-studio"],"summary":"List Versions","description":"Tidligere tilstande af et udkast, nyeste først.\n\nBody'en kommer med, så frontenden kan vise et uddrag uden et ekstra kald\npr. version. Tom liste = intet er overskrevet endnu (eller migration-059\ner ikke kørt) — begge dele er en tom historik, ikke en fejl.","operationId":"list_versions_api_customers__customer_id__articles__content_id__versions_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/versions/{version}/restore":{"post":{"tags":["content-studio"],"summary":"Restore Version","description":"Sæt en tidligere version tilbage som den levende tekst.\n\nGendannelsen tager selv et snapshot af nutiden først — en fortrydelse skal\nkunne fortrydes. Coach og Sprogtjek genberegnes, så panelerne beskriver\nden tekst, der faktisk står der nu, i stedet for den, der stod der før.","operationId":"restore_version_api_customers__customer_id__articles__content_id__versions__version__restore_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/language-fix":{"post":{"tags":["content-studio"],"summary":"Language Fix","description":"Ret de sproglige fejl der kan rettes maskinelt uden at ændre betydningen.\n\nKun typografi (anførselstegn, apostrof-genitiv, decimaltegn,\ntusindtalsseparator, mellemrum før procent). Nabosprogsord, hype og kalker\nrøres ALDRIG — de kræver et sprogligt valg, mennesket skal træffe, og en\nmaskine der omskriver meningen er ikke et sprogtjek.\n\nDeterministisk: ingen LLM, ingen kvote, samme resultat hver gang.","operationId":"language_fix_api_customers__customer_id__articles__content_id__language_fix_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/og/article/{content_id}.png":{"get":{"tags":["content-studio"],"summary":"Article Og Image","operationId":"article_og_image_og_article__content_id__png_get","parameters":[{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}},{"name":"accent","in":"query","required":false,"schema":{"type":"string","default":"","title":"Accent"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/fig/article/{content_id}/{n}.svg":{"get":{"tags":["content-studio"],"summary":"Article Figure Image","operationId":"article_figure_image_fig_article__content_id___n__svg_get","parameters":[{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}},{"name":"n","in":"path","required":true,"schema":{"type":"integer","title":"N"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}},{"name":"accent","in":"query","required":false,"schema":{"type":"string","default":"","title":"Accent"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-preferences":{"get":{"tags":["content-studio"],"summary":"Get Studio Preferences","description":"Studiets valg pr. kunde. `figures`: true = figurer i nye kladder\n(standard), false = ingen figurer.","operationId":"get_studio_preferences_api_customers__customer_id__studio_preferences_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["content-studio"],"summary":"Update Studio Preferences","operationId":"update_studio_preferences_api_customers__customer_id__studio_preferences_patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudioPrefsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/derive":{"post":{"tags":["content-studio"],"summary":"Derive Formats","description":"Artikel → afledte formater. Fail-open pr. format (delvis succes OK).","operationId":"derive_formats_api_customers__customer_id__articles__content_id__derive_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleDerive"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/studio-content/{content_id}/published-check":{"post":{"tags":["content-studio"],"summary":"Published Check","description":"Hent den UDGIVNE side som GPTBot og mål, om den kan citeres.\n\nSvaret gemmes i meta.published_qa, så fladen kan vise sidste resultat uden\nat hente siden igen. Bemærk: `updated_at` røres IKKE — Effekt bruger den\nsom udgivelsestidspunkt, og et QA-tjek er ikke en udgivelse.","operationId":"published_check_api_customers__customer_id__studio_content__content_id__published_check_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/published-check-all":{"post":{"tags":["content-studio"],"summary":"Published Check All","description":"Tjek kundens udgivne sider i én omgang (Effekt-fladen).\n\nEnkelt-tjekket kræver, at nogen åbner artiklen. En noindex, der kommer til\nEFTER udgivelse — ved en temaopdatering, en migrering, et plugin — ville\ningen så opdage. Her er tjekket samlet ét sted, hvor kunden i forvejen\nkigger efter, om indholdet virker.\n\nSider tjekket inden for det seneste døgn springes over, og der køres højst\n_QA_BULK_MAX ad gangen: dette er kundens sider, ikke vores, og vi henter\ndem som en crawler.","operationId":"published_check_all_api_customers__customer_id__published_check_all_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-performance":{"get":{"tags":["content-studio"],"summary":"Content Performance","description":"Effekt pr. udgivet studio-artikel + site-niveau AI-besøg.","operationId":"content_performance_api_customers__customer_id__content_performance_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/optimizer/rewrite":{"post":{"tags":["content-studio"],"summary":"Optimizer Rewrite","description":"Hent en eksisterende side og skriv et forbedret udkast i studiet.","operationId":"optimizer_rewrite_api_customers__customer_id__optimizer_rewrite_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptimizeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feed/articles":{"get":{"tags":["content-studio"],"summary":"Get Articles Feed","description":"Klar-artikler til import som kladder. Auth: X-Client-Key (PULL).\n\nSamme kontrakt-stil som /api/feed/fixes: If-None-Match → 304, ETag altid.\nKun status=ready + forfaldne scheduled — aldrig drafts eller published.","operationId":"get_articles_feed_api_feed_articles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/content-score":{"post":{"tags":["content-studio"],"summary":"App Content Score","description":"Content Coach for en vilkårlig tekst. Auth: X-Client-Key.\n\nBody: {\"title\": \"...\", \"body_markdown\": \"...\" ELLER \"body_html\": \"...\",\n       \"meta_description\": \"...\", \"faq\": [...], \"language\": \"da\"}\nReturnerer `coach_article`s deterministiske tjekliste — samme 23 signaler\nsom studiet selv scorer på. Ingen LLM, intet gemmes: teksten er kundens\nkladde i deres eget CMS, ikke vores indhold.","operationId":"app_content_score_api_apps_content_score_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/apps/content-opportunities":{"get":{"tags":["content-studio"],"summary":"App Content Opportunities","description":"AI-hullerne for denne installations kunde. Auth: X-Client-Key.\n\nSamme kerne som dashboardets rute — de spørgsmål AI'en besvarer uden at\nnævne kunden, plus de øvrige diagnose-kilder. Uden den lukkede løkken\naldrig dér hvor artiklen faktisk skrives.","operationId":"app_content_opportunities_api_apps_content_opportunities_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":8,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feed/articles/status":{"post":{"tags":["content-studio"],"summary":"Report Articles Status","description":"Klienten melder artikel-tilstande hjem. Auth: X-Client-Key.\n\nBody: {articles: [{id, state: \"imported\"|\"published\", post_id?,\n       edit_url?, url?}], plugin_version?}\n- imported  → meta.wordpress = {post_id, edit_url, imported_at}\n              (status uændret — kladden ligger nu i WP).\n- published → status=published + published_url (Effekt starter) +\n              meta.wordpress.published_at.\nIdempotent: gentagne rapporter overskriver blot samme felter.","operationId":"report_articles_status_api_feed_articles_status_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/articles/{content_id}/publish-to-blog":{"post":{"tags":["content-studio"],"summary":"Publish To Blog","description":"Publicér en godkendt artikel til geoa.app/blog (geoas egen dogfood-kunde).\n\nSætter status=published + published_url + meta.blog (slug/youtube_id/noindex)\npå den danske hovedartikel og trækker dens sprogversioner med. Idempotent:\nkald igen (fx med noindex=false) for at forfremme posten ud af noindex-værnet.","operationId":"publish_to_blog_api_customers__customer_id__articles__content_id__publish_to_blog_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogPublish"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series":{"post":{"summary":"Create Series","description":"Opret en serie: skabelon + datasæt valideres, alle sider planlægges\ndeterministisk (titel/slug pr. række), intet genereres endnu.","operationId":"create_series_api_customers__customer_id__series_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Series","operationId":"list_series_api_customers__customer_id__series_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}":{"get":{"summary":"Get Series","operationId":"get_series_api_customers__customer_id__series__series_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}/generate":{"post":{"summary":"Generate Series Batch","description":"Generér næste batch. I review-status er loftet review_target — resten\nkræver menneskets /approve. Hver side er en fuld studio-artikel (kvote,\nPort, Coach); 402 stopper resten ærligt som i by-serien.","operationId":"generate_series_batch_api_customers__customer_id__series__series_id__generate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesGenerate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}/approve":{"post":{"summary":"Approve Series","description":"Menneskets godkendelse af review-batchen: åbner resten af serien og\nflipper staged indexering (noindex,follow → index,follow) på siderne.\n\nproduct_text: en side, fabrikationsvagten har flagget (needs_review),\nspærrer godkendelsen, indtil mennesket enten har rettet den i editoren\neller udtrykkeligt sender dens id i `reviewed`. Vagten er en alarm —\nmen en alarm, ingen behøver kvittere for, er ingen alarm.","operationId":"approve_series_api_customers__customer_id__series__series_id__approve_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SeriesApprove"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}/schedule":{"post":{"summary":"Schedule Series","description":"Drip-publish: N sider pr. dag. Sætter status=scheduled + forskudt\nscheduled_for — /api/feed/articles leverer dem, når de forfalder.\nKun godkendte serier: drip uden review er 'generér 100 sider'-knappen.","operationId":"schedule_series_api_customers__customer_id__series__series_id__schedule_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesSchedule"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}/hub":{"post":{"summary":"Generate Series Hub","description":"Hub-siden: én overbliksartikel der samler og linker hele serien.\nGenereres som almindelig artikel (kvote, Port, Coach) + et deterministisk\nlinkafsnit med ALLE seriens sider.","operationId":"generate_series_hub_api_customers__customer_id__series__series_id__hub_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/products/discovered":{"get":{"summary":"List Discovered Products","description":"Varer hygiejne-sweepet har fundet hos kunden — etape 1's datasæt.\n\nKun ecommerce/b2b_goods; ellers 200 med `reason` og tom liste (ærligt,\nikke en fejl). Hver vare bærer KUN målte felter: url, navn (H1), målt\npris + valuta, billede (og:image eller sidens første <img>), kategori\n(sti-forælder), billedernes alt-tekster og sidetitel. `dataset_rows` er\nklar til POST …/series med series_type=product_text.\n\nLoft: sweepet måler højst page_hygiene.MAX_URLS sider pr. kunde (100),\nså listen dækker op til ~100 varer — det står i `caps`.","operationId":"list_discovered_products_api_customers__customer_id__products_discovered_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/series/{series_id}/deliver":{"post":{"summary":"Deliver Series","description":"Læg godkendte produkttekster på de EKSISTERENDE varesider.\n\nPr. side: SEO-title + meta_description som `meta`-fix og FAQ'en som\n`content_patch`(faq_section) på varens sti — begge via fix-maskinens\ninsert_prepared_fix (checksum-guard, payload-guard, kundens fuldmagt).\nKunden godkender i dashboardet som ethvert andet fix; pluginet anvender\ndem. Brødteksten kan pluginet ikke sætte ind endnu (se\nPRODUCT_TEXT_DELIVERY['body']) — den leveres som copy-paste fra editoren.\nKun godkendte serier, kun sider uden needs_review, kun URL'er på kundens\neget domæne.","operationId":"deliver_series_api_customers__customer_id__series__series_id__deliver_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"series_id","in":"path","required":true,"schema":{"type":"string","title":"Series Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SeriesDeliver"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/repurpose/upload-url":{"post":{"tags":["content-studio"],"summary":"Repurpose Upload Url","description":"Signeret URL, klienten uploader billedet/videoen DIREKTE til.\n\nFilen går aldrig gennem os: en 45 MB video gennem FastAPI ville skulle\nligge i containerens hukommelse, og Cloudflare foran geoa.app afviser\nalligevel bodies over 100 MB.","operationId":"repurpose_upload_url_api_customers__customer_id__repurpose_upload_url_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/social_repurpose__UploadUrlBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/repurpose/intake":{"post":{"tags":["content-studio"],"summary":"Repurpose Intake","description":"Materialet ind → de fem Hv'er ud, plus højst tre spørgsmål.\n\nDet her kald skriver INTET og koster ikke en artikel af kvoten. Kunden\nskal kunne se, hvad vi fik ud af hans opslag, før han bruger en artikel\npå det.","operationId":"repurpose_intake_api_customers__customer_id__repurpose_intake_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/repurpose/article":{"post":{"tags":["content-studio"],"summary":"Repurpose Article","description":"De fem Hv'er → brief → artikel, gennem den almindelige vej.\n\nKvote, Porten, Coach, feed og udgivelse følger med, fordi vi kalder\ngenerate_article_core og ikke en kopi af den.","operationId":"repurpose_article_api_customers__customer_id__repurpose_article_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArticleBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content/{content_id}/photo-suggestions":{"get":{"tags":["content-studio"],"summary":"Photo Suggestions","description":"Kundens egne billeder, rangeret mod artiklen. Fail-open: tomt array.","operationId":"photo_suggestions_api_customers__customer_id__content__content_id__photo_suggestions_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content/{content_id}/media":{"post":{"tags":["content-studio"],"summary":"Add Media","description":"Sæt ét billede ind i artiklen. Idempotent på URL'en.","operationId":"add_media_api_customers__customer_id__content__content_id__media_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content/{content_id}/media/upload-url":{"post":{"tags":["content-studio"],"summary":"Media Upload Url","description":"Signeret upload-URL til artikeleditoren — samme form som\n/repurpose/upload-url, samme lager (media_store). Ingen ny lagring.","operationId":"media_upload_url_api_customers__customer_id__content__content_id__media_upload_url_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/article_media__UploadUrlBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content/{content_id}/photos/generate":{"post":{"tags":["content-studio"],"summary":"Generate Photos","description":"To AI-fotos til artiklen. Begge gemmes i lageret og i `meta.ai_photos`;\nkunden vælger ét via /media. Lofter håndhæves her.","operationId":"generate_photos_api_customers__customer_id__content__content_id__photos_generate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"content_id","in":"path","required":true,"schema":{"type":"string","title":"Content Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-clusters":{"get":{"summary":"Content Clusters Map","description":"Cluster-kortet: pillars med deres støtteartikler, dækning og 'næste\nartikel'-forslag (opportunities token-matchet mod pillar'ens titel).\n\nArtikler uden cluster ligger i `unclustered`, så kortet også viser det\nærlige nul — et kort der kun viser de pæne klynger, lyver om resten.","operationId":"content_clusters_map_api_customers__customer_id__content_clusters_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-clusters/assign":{"post":{"summary":"Assign Cluster","description":"Knyt en artikel til et cluster (eller løsn den med pillar_id=null).\nBody: {content_id, pillar_id | null}. Pillar'en skal være kundens egen\nartikel — et fremmed id ville lave krydslinks til en anden kundes indhold.","operationId":"assign_cluster_api_customers__customer_id__content_clusters_assign_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-clusters/plan":{"post":{"summary":"Plan Cluster","description":"Body: {head_term, language?, spokes?}. Returnerer planen UDEN at gemme\nnoget — kunden redigerer og sender den til /plan/apply. Ingen LLM:\nplanen er en funktion af kundens PAA-spørgsmål, AI-huller og skabeloner.\nUkendt sprog → engelsk (market_locale-kontrakten).","operationId":"plan_cluster_api_customers__customer_id__content_clusters_plan_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-clusters/plan/apply":{"post":{"summary":"Apply Cluster Plan","description":"Body = planen fra /plan (evt. redigeret af kunden; også accepteret som\n{plan: {...}}). Opretter pillar-brief + spoke-briefs; spokes bærer\nsource.cluster.pillar_id, så artiklerne fødes IND i klyngen og\nkrydslinkes af den eksisterende mekanisme. Højst 1 pillar + 8 spokes pr.\nkald — seriemotorens review-princip, aldrig \"opret 50 på én gang\".\n\nKvote: PLAN_ARTICLE_QUOTA måles frem i tiden — en plan på ni sider hos en\nkunde med tre artikler tilbage logges (MONITOR) og afvises med 402 kun\nnår STUDIO_QUOTA_ENFORCE er sat. Selve artikelkvoten håndhæves stadig\nved hver generering; dette er advarslen FØR kunden godkender ni briefs.","operationId":"apply_cluster_plan_api_customers__customer_id__content_clusters_plan_apply_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/benchmarks":{"get":{"tags":["benchmarks"],"summary":"Get Benchmark","operationId":"get_benchmark_api_benchmarks_get","parameters":[{"name":"industry","in":"query","required":true,"schema":{"type":"string","minLength":2,"title":"Industry"}},{"name":"country","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"title":"Country"}},{"name":"city","in":"query","required":false,"schema":{"type":"string","default":"","title":"City"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/benchmarks/coverage":{"get":{"tags":["benchmarks"],"summary":"Benchmark Coverage","description":"Segmenter med nok data (n ≥ N_MIN) — så flader ved hvad de kan vise.","operationId":"benchmark_coverage_api_benchmarks_coverage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/llms-fetch-log":{"get":{"summary":"Admin Llms Fetch Log","description":"Forsøg B: hvem henter vores llms.txt? (startet 2026-07-28)\n\nOptælling pr. user-agent-klasse og vært, med Ahrefs' referencetal i samme\nsvar, så vores eget resultat aldrig sammenlignes mod et husket tal.\nHERMES-guarded — der er ingen global gate, hver rute skal selv have den.","operationId":"admin_llms_fetch_log_admin_llms_fetch_log_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/plan":{"get":{"summary":"Me Plan","description":"Plan status for the GEO frontend's `useSubscription()`.\n\nResolution: verified Supabase user JWT (Authorization: Bearer) first,\nelse the explicit ?email= parameter. Always 200 — degrades to plan null\nso the frontend can fall back to its own Stripe row.","operationId":"me_plan_api_me_plan_get","parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","default":"","title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/debug-templates":{"get":{"summary":"Debug Templates","description":"Debug: check template paths.","operationId":"debug_templates_debug_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/dashboard":{"get":{"summary":"Dashboard Redirect","description":"Legacy route — redirect to the platform dashboard.\n\nUsed to render an unauthenticated customer-selector listing ALL\ncustomers (multi-tenant leak). The real dashboard lives on\nplatform.geoa.app, which handles auth and shows only the user's own.","operationId":"dashboard_redirect_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin":{"get":{"summary":"Admin Dashboard","description":"Internal admin dashboard (old root).","operationId":"admin_dashboard_admin_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api/gsc-own/snapshot":{"post":{"summary":"Gsc Own Snapshot Now","description":"Trig et egen-domæne GSC-snapshot NU (HERMES-beskyttet). Lader Martin\nverificere GSC-setup med det samme i stedet for at vente på mandags-jobbet.\nReturnerer 'not_configured' hvis GSC_SA_JSON ikke er sat (fail-open).","operationId":"gsc_own_snapshot_now_api_gsc_own_snapshot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/customers":{"get":{"summary":"My Customers","description":"Return the signed-in user's OWN customers (scoped by verified JWT).\n\nPreviously returned all customers unconditionally (\"Lovable handles\naccess control\") — that leaked every workspace to every user. Now:\nno verified JWT, or no linked customers → empty list.","operationId":"my_customers_api_me_customers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/overview":{"get":{"summary":"My Overview","description":"Multi-customer overview with scores, trends, and status — scoped to\nthe signed-in user's own customers (verified JWT; fail-closed empty).","operationId":"my_overview_api_me_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/subscription":{"get":{"summary":"My Subscription","description":"Return subscription info for the signed-in user's OWN customers\n(scoped by verified JWT; fail-closed to a free/empty shape).","operationId":"my_subscription_api_me_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/connector-key":{"post":{"summary":"My Connector Key Mint","description":"Selvbetjent Claude-connector: mønt (eller rotér) kundens nøgle.\n\nBody: {\"customer_id\": \"...\"}. JWT-vejen (bruger ejer kunden) — HERMES\nrører aldrig connectoren. Rotation er indbygget i _mint_installation:\nen tidligere claude-nøgle for samme kunde revokeres. Nøglen vises ÉN\ngang (hash-only lagring) — frontend viser den færdige connector-URL.","operationId":"my_connector_key_mint_api_me_connector_key_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"get":{"summary":"My Connector Key Status","description":"Status for kundens Claude-connector (aldrig selve nøglen — hash-only).","operationId":"my_connector_key_status_api_me_connector_key_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/questions":{"get":{"summary":"My Questions","description":"List the user's tracked questions for one of THEIR customers + usage.","operationId":"my_questions_api_me_questions_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"My Questions Add","description":"Add tracked question(s) to one of the user's OWN customers, limit-enforced.\n\nBody: {\"customer_id\":\"...\",\"question\":\"...\"} for one question, or BULK\n(mass-paste): {\"customer_id\":\"...\",\"questions\":[\"...\",\"...\"]} — a multi-line\n\"question\" string is also split on newlines. 402 at the plan ceiling with\n{\"error\":\"question_limit_reached\", ...} (the upgrade trigger); a bulk add\nthat partially fits returns 200 with added/skipped counts.","operationId":"my_questions_add_api_me_questions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"My Questions Remove","description":"Remove a tracked question from one of the user's OWN customers.\n\nBody: {\"customer_id\":\"...\",\"question\":\"...\"}. 200 on success; 404 if not\ntracked; 403 if the customer is not linked to the user.","operationId":"my_questions_remove_api_me_questions_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/questions/regenerate":{"post":{"summary":"My Questions Regenerate","description":"Lav nye udledte spørgsmål ud fra kundens NUVÆRENDE profil.\n\nBody: {\"customer_id\": \"...\"}. Kun automatikkens egne rækker\n(source='derived') udskiftes — kundens egne spørgsmål står, og de lægges\nførst under plan-loftet. Fejler genereringen, står det gamle sæt uændret,\nog svaret siger hvorfor (422), i stedet for at se ud som om det virkede.","operationId":"my_questions_regenerate_api_me_questions_regenerate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/brands":{"get":{"summary":"My Brands","description":"Kundens sporede navne + forbrug/loft.","operationId":"my_brands_api_me_brands_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"My Brands Upsert","description":"Tilføj/opdatér ét sporet navn på en af brugerens EGNE kunder.\n\nBody: {\"customer_id\":\"...\",\"name\":\"AddonNordic\",\n       \"aliases\":[\"Addon Nordic\"],\"primary\":true}\n`aliases` er stavevarianter af SAMME navn (AI'en staver som den vil) og\ntæller ikke med i plan-loftet. 402 ved loftet med\n{\"error\":\"brand_limit_reached\", ...}.\n\nSvaret bærer `status` + `changed`. Findes navnet allerede i en ANDEN\nskrivemåde, rettes den gemte form (\"respelled\", `previous_name` med) —\nmatchningen er stadig case-insensitiv, det er kun den form vi MÅLER og\nUDGIVER under, der følger kundens valg. Er der intet at gemme, er svaret\n\"unchanged\"/`changed:false`, og fladen skal sige det i stedet for \"Gemt\"\n(dogfood 19/9: \"PartnerDecode\" oven på \"partnerdecode\" kvitterede \"Navn\ntilføjet\" og gemte ingenting).","operationId":"my_brands_upsert_api_me_brands_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"My Brands Remove","description":"Fjern ét sporet navn. Body: {\"customer_id\":\"...\",\"name\":\"...\"}.","operationId":"my_brands_remove_api_me_brands_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/onboarding-confirmation":{"get":{"summary":"My Onboarding Confirmation","description":"Alt hvad bekræftelseskortet skal vise, med proveniens pr. del.","operationId":"my_onboarding_confirmation_api_me_onboarding_confirmation_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Confirm Onboarding","description":"Skriv kundens bekræftelse — alt det viste bliver source='user'.\n\nUden reanalyse: næste scan bruger valgene (mod POST /archetype-vejen, der\ngenkører hele analysen — to identiske input må ikke have vidt forskellig\nkostpris, jf. §2.5). Delene er hver især best-effort med status i svaret;\nkun selve confirmed_at-stemplet er obligatorisk.","operationId":"confirm_onboarding_api_me_onboarding_confirmation_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingConfirmation"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/activation/event":{"post":{"summary":"Activation Event","description":"B8 (fælles wizard): klienten melder, at kunden SÅ noget — result_rendered\n(\"x af y\" på egne data) og draft_opened. Rapportens §6.5 spærrer\nsnydevejen: aktivering tælles ved RENDER, ikke når endpointet svarer.\nÉn gang pr. (kunde, hændelse). Kun de to hændelser onboarding_funnel\nkender; alt andet 400. Auth: HERMES eller ejerens JWT.","operationId":"activation_event_api_customers__customer_id__activation_event_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivationEventIn"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/start-scan-attach":{"post":{"summary":"Start Scan Attach","description":"B2 (fælles wizard, 2026-09-13): aflevér start-scannet FØR konto ind i\nkontoen — ydelser til Brand Brain (derived), spørgsmål (derived),\nkonkurrenter AI nævnte i stedet, og hver måling som første datapunkt.\n\nAuth: HERMES eller ejerens bruger-JWT (_authorize_dashboard_write).\n409 hvis scannets domæne ikke er kundens. 404 hvis scannet er udløbet\n(én times TTL) — så starter frontenden et nyt i stedet for at vise tomt.","operationId":"start_scan_attach_api_customers__customer_id__start_scan_attach_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartScanAttach"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/onboarding-checklist":{"get":{"summary":"My Onboarding Checklist","description":"B7 (fælles wizard, 2026-09-13): ÉN tjekliste for begge dashboards.\n\nDe fem ting kun kunden kan (tal, forfatter, GSC, første fix, udgivet),\nudledt af de samme tabeller som setup_digest — så AI SEO, Content og\nMartins digest aldrig er uenige. `first_draft` er B4's brief, hvis det\nfindes. Findes det ikke, men ydelserne er på plads, lægges det klar i\nbaggrunden (næste kald har det) — fail-open, aldrig en fejl her.\nRen læsning for kunden; skrivningen sker kun i baggrunden og kun én gang.","operationId":"my_onboarding_checklist_api_me_onboarding_checklist_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me/usage":{"get":{"summary":"My Usage","description":"Usage stats for plan progress in sidebar (monthly window).\n\nAn \"analysis\" = one full GEO-scan (one geo_scores row) of one of the\nuser's own customer domains this calendar month. Public free-checker\nscans of arbitrary domains do NOT count.\n\nUser resolution mirrors /api/me/plan: verified Supabase JWT first, else\n?email= (only honored behind the HERMES gate). Without either, falls\nback to platform-wide customer-domain usage (legacy behavior, minus\nfree-checker noise).","operationId":"my_usage_api_me_usage_get","parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","default":"","title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dashboards":{"get":{"summary":"Bulk Dashboards","description":"Bulk dashboard — return overview for multiple customers in one call.\n\nAvoids N calls when a user has 5+ customers on /app overview.\nMax 20 ids per call. Requested ids are intersected with the signed-in\nuser's own customers (verified JWT) — no JWT or no overlap → empty.","operationId":"bulk_dashboards_api_dashboards_get","parameters":[{"name":"ids","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated customer UUIDs, max 20","default":"","title":"Ids"},"description":"Comma-separated customer UUIDs, max 20"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/debug":{"get":{"summary":"Debug","operationId":"debug_debug_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/debug/perplexity":{"get":{"summary":"Debug Perplexity","operationId":"debug_perplexity_debug_perplexity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/transfer-ownership":{"post":{"summary":"Admin Transfer Ownership","description":"Flyt ejerskab af en kunde (domæne) til en anden bruger — admin/support.\n\nHERMES-beskyttet. Bruges når samme person har flere logins, eller når\nsupport manuelt skal flytte et domæne mellem konti (i stedet for\ndatabase-kirurgi). Body:\n  {\"domain\": \"acme.dk\"}            — eller {\"customer_id\": \"<uuid>\"}\n  {\"new_owner_user_id\": \"<uuid>\"}  — påkrævet\n  {\"old_owner\": \"demote\"|\"remove\"} — valgfri, default \"demote\" (→ rolle\n                                      'admin', beholder adgang)\n\nRækkefølgen respekterer unik-indekset user_customers_one_owner_per_customer:\ngammel ejer nedgraderes/fjernes FØR ny ejer indsættes/forfremmes.","operationId":"admin_transfer_ownership_api_admin_transfer_ownership_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/costs":{"get":{"summary":"Admin Costs","description":"Real cost report aggregated from the api_cost_events ledger (COST_MODEL §6A).\n\nHERMES-protected (same gate as the other /api/admin routes). ADDITIVE — a new\nread-only route; touches no existing response shape.\n\nQuery param `days` (default 30, 1..365). Returns total real spend\n(actual-first, est fallback) plus breakdowns by service, by day, by operation\n(estimated) and top customers (estimated). Vendor rows and operation rows are\naggregated separately so per-call actuals and whole-operation estimates are\nnever summed together (no double-counting). See cost_ledger.aggregate_cost_rows.\n\nPAGINERET (2026-08-24): PostgREST håndhæver Supabase' server-side `max-rows`\n(1000) og skærer stille et større `limit` ned — et enkelt kald gav derfor\naltid præcis 1000 rækker og underrapporterede ugekosten tavst. Vi sider nu\nigennem med offset og melder `truncated` hvis loftet nås, så en afkortet\nrapport aldrig igen ser ud som en komplet rapport.\n\nFail-open: if the ledger is unreachable it returns ok=false with empty\naggregates rather than erroring — this is telemetry, not a user data path.","operationId":"admin_costs_api_admin_costs_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customers":{"post":{"summary":"Create Customer","description":"Async onboarding — returns immediately, heavy work runs in background.\n\nIdempotent: if domain already exists, returns 200 with existed:true.\nAdgang: HERMES-nøgle eller et gyldigt bruger-JWT (der er intet kunde-id at\nbinde ejerskab til endnu — ruten opretter det). Den offentlige tragt går\ngennem /api/exit-capture, ikke herind.\n\nLite/funnel mode (body `{\"lightweight\": true}` or `?lite=true` /\n`?lightweight=true`) provisions the customer + cheap crawl only, skipping\nall paid AI/search steps (Perplexity visibility,\nmulti-platform, content generation, competitor discovery, GBP) and the\nonboarding email. Cuts install cost ~10x for funnel apps that never display\nthat output. The full pipeline still runs on the normal (non-lite) path.","operationId":"create_customer_customers_post","parameters":[{"name":"lite","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Lite"}},{"name":"lightweight","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Lightweight"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/generate-professional-content":{"post":{"summary":"Generate Professional Content Endpoint","description":"Generate content for Professional plan customers.\n\nIf customer_id is provided: generate content for that customer (if Professional plan).\nIf not: run weekly pipeline for ALL Professional plan customers.\n\nExpected payload:\n{\n    \"customer_id\": \"uuid\"  # optional\n}","operationId":"generate_professional_content_endpoint_api_generate_professional_content_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhooks/creem":{"post":{"summary":"Creem Webhook","description":"Udfaset billing-webhook. Muterer intet — 410 uanset krop og headere.","operationId":"creem_webhook_api_webhooks_creem_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"deprecated":true}},"/status/{customer_id}":{"get":{"summary":"Onboarding Status","description":"Check if async onboarding is complete.","operationId":"onboarding_status_status__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/visibility/{customer_id}":{"get":{"summary":"Check Visibility","description":"Per-engine AI-visibility for a customer.\n\n`?cached=true` → free read of the last stored snapshot (no AI calls; safe for\nthe free tier). Default (live) recomputes via Perplexity and is METERED —\ngate it behind a paid plan / explicit refresh.","operationId":"check_visibility_visibility__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"cached","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Cached"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/visibility/{customer_id}/use-case-scan":{"post":{"summary":"Use Case Scan Endpoint","description":"Quick-scan af KUN kundens use_case_queries (2-5 stk., parallelt).\n\nBestilt af Lovable til first-run gap-reveal (docs i geo-platform-lovable/\ndocs/hermes-requests/2026-07-usecase-quick-scan.md): svar på få sekunder\nhvor fuld /visibility tager ~30s. Samme details[]-kontrakt + ærlig\ndegradering (degraded_from/perplexity_error). Billigere end fuld scan —\nscanner højst 5 queries. Tom use_case_queries → 409 med tydelig grund\nså frontenden skipper steppet i stedet for at vise en tom tilstand.\nADDITIV — ingen eksisterende kontrakter er ændret.","operationId":"use_case_scan_endpoint_visibility__customer_id__use_case_scan_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/visibility/{customer_id}/use-case-scan/latest":{"get":{"summary":"Use Case Scan Latest Endpoint","description":"Sidste GEMTE quick-scan — gratis DB-læsning, ingen AI-kald, ingen kvote.\n\nFirst-run spurgte \"Kør min FØRSTE synligheds-måling\" hver gang guiden blev\nvist, også for en kunde der havde målt samme dag — og klikket koster kvote\n(F15, dogfood wonmetrics 2026-08-30). Rækkerne fandtes allerede i\nai_scan_results (`source='usecase_scan'`); det der manglede var vejen til\nat læse dem tilbage. Samme `details[]`-kontrakt som POST-scannet, så\nfrontenden genbruger sin parser 1:1.\n\n`empty: true` = aldrig målt. Det er IKKE det samme som \"målt, nul omtaler\"\n— de to skal sige vidt forskellige ting til kunden.\nADDITIV — rører ingen eksisterende kontrakt.","operationId":"use_case_scan_latest_endpoint_visibility__customer_id__use_case_scan_latest_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/status":{"get":{"summary":"Customer Status Endpoint","description":"Lightweight status check for polling — returns stage, progress_pct, eta.\n\nUsed by frontends to poll until onboarding/analysis completes.\nFollows the Lovable-requested format:\n{ customer_id, status, stage, progress_pct, eta_seconds, updated_at, error }","operationId":"customer_status_endpoint_api_customers__customer_id__status_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}":{"patch":{"summary":"Update Customer","description":"Update customer fields (company_name, domain).\n\nAdgang: kalderen skal eje kunden (eller bære HERMES-nøglen). Stod indtil\n2026-08-17 markeret som åben med adgangsstyring hos Lovable — og enhver\nkunne derfor omdøbe enhver kunde.","operationId":"update_customer_api_customers__customer_id__patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Customer","description":"Delete a customer and all associated data.\n\nAdgang: kalderen skal eje kunden (eller bære HERMES-nøglen). Verificeret i\nprod 2026-08-17, FØR værnet kom på: et kald helt uden legitimation fik\n`200 {\"success\":true,\"message\":\"Kunden er slettet\"}`.\n\nAbonnements-værn (prod-hændelse 2026-08-19): bærer rækken et aktivt\nabonnement, flyttes plan-felterne til en anden af brugerens rækker før\nsletning — findes ingen egnet række, afvises sletningen med 409, så\nabonnementet aldrig efterlades uden bærer.","operationId":"delete_customer_api_customers__customer_id__delete","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/reanalyze":{"post":{"summary":"Reanalyze Customer","description":"Alias for POST /api/customers/{customer_id}/refresh.\n\nManuel re-analyse — bruger trykker \"Kør analyse igen\".\nRespekterer credits/rate-limit.","operationId":"reanalyze_customer_api_customers__customer_id__reanalyze_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/actions/generate":{"post":{"summary":"Generate Actions","description":"Generate actions from the latest GEO Score data.\n\nUsed for manual re-generation (e.g. after reanalyze).\nClears old open actions and generates fresh ones.\nReturns the new actions.","operationId":"generate_actions_api_customers__customer_id__actions_generate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/actions":{"get":{"summary":"List Actions","description":"Get prioritized action plan for a customer.\n\nReturns actions sorted by priority (critical first) then impact_score.\nSupports filtering by status, category, and priority.","operationId":"list_actions_api_customers__customer_id__actions_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"priority","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/actions/{action_id}":{"patch":{"summary":"Update Action","description":"Update action status (and optional note).\n\nStatus values: open, in_progress, done, dismissed","operationId":"update_action_api_customers__customer_id__actions__action_id__patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/business-value":{"post":{"summary":"Get Business Value","description":"Calculate business value from dimensions (or auto-detect from latest geo_score).\n\nIf dimensions are provided, calculate from those.\nIf not, fetch the latest GEO Score dimensions automatically.","operationId":"get_business_value_api_customers__customer_id__business_value_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessValueRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/opportunities":{"get":{"summary":"List Opportunities","description":"Find visibility opportunities — prompts where the customer is NOT mentioned\nbut competitors are, sorted by estimated search volume (highest first).\n\nReturns prioritized opportunities with search volume, competitor names,\nmissing reason classification, and links to existing actions.","operationId":"list_opportunities_api_customers__customer_id__opportunities_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"days_back","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days Back"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/prompts":{"get":{"summary":"List Prompts","description":"Search, filter, and paginate through AI prompt results.\n\nFull-text search on prompt_text, filter by platform or mention status,\nwith pagination and enriched response excerpts.\n\nQuery params:\n  q — full-text search on prompt_text\n  platform — filter: chatgpt, gemini, perplexity, claude\n  mentioned — filter: true (mentioned) or false (not mentioned)\n  page — page number (1-indexed, default 1)\n  limit — results per page (max 100, default 20)\n  sort_by — sort column: created_at, sentiment_score, is_mentioned\n  sort_order — sort direction: asc, desc (default desc)\n\nAdditivt felt `is_aided` pr. række + aided-felterne i `summary`\n(2026-09-19): nævnte SPØRGSMÅLET selv brandet, så svaret gentager navnet\nper konstruktion? Så tæller rækken ikke i `mention_rate` — samme regel og\nsamme nævner som `/share-of-voice/{id}/summary`, der ellers svarede\n0,0 % samme dag, som denne flade skrev 37 % (dogfood PartnerDecode).\n`summary.aided_excluded` + `aided_mentioned` bærer de udeladte svar, så\nde fire grupper går op i `total_prompts`, og `status`/`reason` skelner\n\"kun aided\" fra \"aldrig nævnt\". `mention_rate` er None (ikke 0) når intet\nunaided svar er vurderet.","operationId":"list_prompts_api_customers__customer_id__prompts_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"platform","in":"query","required":false,"schema":{"type":"string","default":"","title":"Platform"}},{"name":"mentioned","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mentioned"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","default":"created_at","title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/prompts/{prompt_id}":{"get":{"summary":"Get Prompt","description":"Get full details for a single prompt result.\n\nReturns the complete raw_response and all metadata for detailed view.","operationId":"get_prompt_api_customers__customer_id__prompts__prompt_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"prompt_id","in":"path","required":true,"schema":{"type":"string","title":"Prompt Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/simulate":{"post":{"summary":"Impact Simulate","description":"Simulate GEO Score, leads, and revenue impact of implementing actions.\n\nTakes a list of action_ids and projects:\n- New GEO Score (simulating each dimension fixed to ≥70)\n- Monthly leads recovered\n- Monthly revenue impact (DKK)\n- Traffic increase\n- Annualized revenue projection\n\nUses Geoa's average benchmarks for Danish SMBs.","operationId":"impact_simulate_api_customers__customer_id__simulate_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImpactSimulateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/alerts":{"get":{"summary":"List Alerts","description":"Get AI alerts for a customer.\n\nSupports filtering by unread_only, severity, and alert_type.\nSorted by fired_at descending. Afviste alerts udelades, medmindre\ninclude_dismissed=true (se fetch_alerts).\n\n`lang` styrer sproget i titel og body. Sendes den ikke, afgør backenden\ndet via sprogstigen (kundens eget valg → sitets sprog → landet), så en\nsvensk kunde ikke længere læser danske advarsler — se `alert_i18n`.","operationId":"list_alerts_api_customers__customer_id__alerts_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread Only"}},{"name":"severity","in":"query","required":false,"schema":{"type":"string","default":"","title":"Severity"}},{"name":"alert_type","in":"query","required":false,"schema":{"type":"string","default":"","title":"Alert Type"}},{"name":"include_dismissed","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Dismissed"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/alerts/unread-count":{"get":{"summary":"Alerts Unread Count","description":"Get count of unread, non-dismissed alerts for the bell badge.","operationId":"alerts_unread_count_api_customers__customer_id__alerts_unread_count_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/alerts/{alert_id}/read":{"patch":{"summary":"Mark Alert Read Endpoint","description":"Mark an alert as read.","operationId":"mark_alert_read_endpoint_api_customers__customer_id__alerts__alert_id__read_patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/alerts/{alert_id}/dismiss":{"patch":{"summary":"Dismiss Alert Endpoint","description":"Dismiss an alert.","operationId":"dismiss_alert_endpoint_api_customers__customer_id__alerts__alert_id__dismiss_patch","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/actions/{action_id}/auto-fix":{"post":{"summary":"Trigger Auto Fix","description":"Generate auto-fix content for an auto_fixable action.\n\nGenerates robots.txt content, llms.txt, or security header config\ndepending on the action's source_dimension.\n\nReturns the generated fix content + instructions.\n\n`?lang=da|no|sv|en` (valgfrit) styrer sproget i instruktionerne. Uden\nparameteren udledes det af kundens `country_code` — og uden country_code\ner svaret dansk som hidtil. Det GENEREREDE indhold følger derimod det\nanalyserede sites eget sprog (se auto_fix_engine-docstringen).","operationId":"trigger_auto_fix_api_customers__customer_id__actions__action_id__auto_fix_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/actions/{action_id}/auto-fix/confirm":{"post":{"summary":"Confirm Auto Fix","description":"Confirm that the auto-fix has been applied.\n\nMarks the action as 'done' with auto_fix_status='completed'.","operationId":"confirm_auto_fix_api_customers__customer_id__actions__action_id__auto_fix_confirm_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"action_id","in":"path","required":true,"schema":{"type":"string","title":"Action Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitors/scan":{"post":{"summary":"Competitors Scan","description":"Run GEO Score on all competitor domains (parallel, max 3 concurrent).\n\nStores results in competitor_scores table for comparison.\nReturns per-competitor scores and errors.","operationId":"competitors_scan_api_customers__customer_id__competitors_scan_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitors/comparison":{"get":{"summary":"Competitors Comparison","description":"Get side-by-side GEO Score comparison: customer vs all competitors.\n\nReturns customer's latest score, competitor scores, and per-dimension\ngap analysis showing where competitors outperform.","operationId":"competitors_comparison_api_customers__customer_id__competitors_comparison_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitors/gaps":{"get":{"summary":"Competitors Gap Analysis","description":"Get enhanced gap analysis — specific advantages per competitor.\n\nFor each competitor, identifies their strongest dimensions vs the\ncustomer's weakest, with actionable focus recommendations.","operationId":"competitors_gap_analysis_api_customers__customer_id__competitors_gaps_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitor-sov":{"get":{"summary":"Competitors Share Of Voice","description":"Citation Share-of-Voice: the customer's brand vs its competitors, per AI\nengine and over time — computed from the stored multi-engine scans\n(ai_scan_results), detecting competitor mentions in each answer with no\nextra AI calls. Returns status ok | no_competitors | no_scans.\n\nMoney-lag (Fase 2.2, monitor-først): competitor_tracking er en betalt\nfeature. Uden entitlement OG med COMPETITOR_GATE_ENFORCE sat returneres en\nTEASE (brandets tal + maskerede konkurrenter + locked:true) — aldrig en\nnøgen mur. Uden flaget logges kun (would_lock), svaret er uændret.\n\nGuarded (HERMES, monitor-first): the Lovable dashboard sends the key; direct\ncallers get 401 once HERMES_API_KEY is set. New dashboard routes must guard\nper the repo's auth rules.","operationId":"competitors_share_of_voice_api_customers__customer_id__competitor_sov_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitors":{"get":{"summary":"Competitors List","description":"List the competitors tracked for a customer.","operationId":"competitors_list_api_customers__customer_id__competitors_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Competitors Add","description":"Manually add a competitor (idempotent on domain).","operationId":"competitors_add_api_customers__customer_id__competitors_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompetitorAdd"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/competitors/{competitor_id}":{"delete":{"summary":"Competitors Delete","description":"Remove a competitor. Scoped by both id AND customer_id so one customer\ncan never delete another's row.","operationId":"competitors_delete_api_customers__customer_id__competitors__competitor_id__delete","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"competitor_id","in":"path","required":true,"schema":{"type":"string","title":"Competitor Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/backfill-fixes":{"post":{"summary":"Admin Backfill Fixes","description":"Kør auto_prepare_fixes for eksisterende kunder (engangs-efterslæb).\n\nFix-genereringen lå sidst i pipelinens try-blok og blev ædt af upstream-\nfejl — derfor havde næsten ingen kunder pending fixes trods friske\nllms-configs. Idempotent (checksum-guard i _auto_insert_pending); rører\naldrig godkendte fixes. only_empty=True springer kunder med eksisterende\nfixes over.","operationId":"admin_backfill_fixes_api_admin_backfill_fixes_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"only_empty","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Only Empty"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/repair-competitor-names":{"post":{"summary":"Admin Repair Competitor Names","description":"Ryd konkurrent-navne der er soegeresultat-titler i stedet for firmanavne.\n\nAuto-fundne konkurrenter fik indtil 2026-08-30 Braves sidetitel som navn.\nHos wonmetrics.com (betalende Business-kunde) hed fire af seks sporede\nkonkurrenter ting som \"10 Best Tender Management Softwares: Ranked &\nReviewed\" — DOMAENERNE var rigtige hele vejen, kun opløsningen domaene →\nnavn var forkert. Rettelsen i scraper.py gaelder fra naeste scan; denne\nrute rydder det der allerede staar i tabellen.\n\nIdempotent: rører kun raekker hvis nuvaerende navn falder paa\nbrand_name-vaernet. Et registerberiget navn (company_number sat) er\nkorroboreret og roeres aldrig. `dry_run=true` (default) skriver intet.","operationId":"admin_repair_competitor_names_api_admin_repair_competitor_names_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":500,"title":"Limit"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/backfill-competitors":{"post":{"summary":"Admin Backfill Competitors","description":"Re-run competitor discovery for existing customers whose competitors were\nnever stored (the historical silent-write-failure backlog). Sequential and\nidempotent — discover_competitors skips already-tracked domains. Requires a\nprofile with services (else discovery no-ops).","operationId":"admin_backfill_competitors_api_admin_backfill_competitors_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"only_empty","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Only Empty"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/internal/plan-sync":{"post":{"summary":"Plan Sync Webhook","description":"Receive subscription events from Lovable Cloud.\n\nVerifies X-Lovable-Signature (HMAC-SHA256), then upserts plan/tier/quotas\non all customers owned by the specified user.\n\nReturns 200 quickly — actual updates run synchronously but are lightweight.","operationId":"plan_sync_webhook_api_internal_plan_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/refresh":{"post":{"summary":"Refresh Customer","description":"Re-run the full analysis pipeline for a customer.\n\nLaunches async onboarding pipeline (same as initial onboarding).\nReturns immediately — use GET /api/customers/{customer_id}/status to poll.","operationId":"refresh_customer_api_customers__customer_id__refresh_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/report/{customer_id}":{"get":{"summary":"Weekly Report","description":"Weekly GEO report — standardized format with time-series history.","operationId":"weekly_report_report__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/trend/{customer_id}":{"get":{"summary":"Customer Trend","description":"Visibility trend over time — standardized time-series format.\n\nReturns sorted series of historical geo_scores for this customer.","operationId":"customer_trend_trend__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gaps/{customer_id}":{"get":{"summary":"Content Gaps","description":"Content gap analysis — find keywords where competitors win + generate gap-closing content.","operationId":"content_gaps_gaps__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gbp/{customer_id}":{"get":{"summary":"Gbp Check","description":"Google Business Profile analysis — now with weekly activity micro-actions.\n\nÉN opløsning, ét svar (2026-09-03): denne rute og /api/reputation/{id}\ntegner den samme virksomhed på to faner af samme side. Indtil nu kørte\ndenne rute et live-opslag ved hver visning (~60-80 s, budget-afkortet),\nmens omdømme-fanen serverede nat-sweepets snapshot — to lagre, to svar,\nog på flereanmeldelser.dk to modstridende eksistens-påstande på samme\nskærm. Nu læser begge faner det SAMME snapshot; ?refresh=1 tvinger et\nfriskt opslag, og resultatet skrives til begge lagre, så de ikke kan\ndrive fra hinanden igen.","operationId":"gbp_check_gbp__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force a fresh (paid, ~60s) live check","default":false,"title":"Refresh"},"description":"Force a fresh (paid, ~60s) live check"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reputation/public":{"get":{"summary":"Reputation Check Public","description":"Public reputation check for et råt domæne (prospect, ingen kunde).\n\nSamme svar-shape som /api/reputation/{customer_id} minus customer-felterne.\nTrustpilot-pending-kontrakten genbruges uændret: status=\"pending\" +\ntask_id → kalderen fortsætter med ?tp_task_id= (GBP genbruges fra cache).\n\n`lang` (2026-09-15): det svenske rephub-site (fleromdomen.se) indsætter\n`recommendations` ordret — uden sproget fik svenske besøgende dansk. sv\nslår også GBP op på svensk Google (kategorier på svensk). Begge caches\ner nøglet på (sprog, domæne), så da og sv aldrig serverer hinandens svar.\nUkendt sprog → da, så eksisterende kaldere er uberørte.","operationId":"reputation_check_public_api_reputation_public_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"tp_task_id","in":"query","required":false,"schema":{"type":"string","description":"Continue a pending Trustpilot task","default":"","title":"Tp Task Id"},"description":"Continue a pending Trustpilot task"},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"Language for recommendations and notes: da (default) or sv","default":"da","title":"Lang"},"description":"Language for recommendations and notes: da (default) or sv"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reputation/{customer_id}":{"get":{"summary":"Reputation Check","description":"Combined reputation check — Google Business Profile + Trustpilot.\n\nServerer som udgangspunkt det GEMTE snapshot fra nat-sweepet\n(gbp_activity.activity_data.reputation) øjeblikkeligt — feltet\n`checked_at` + `cached: true` fortæller frontend hvor frisk det er.\nLive-opslaget (GBP ~48s + Trustpilot-task, målt ~69s pr. sidevisning)\nkøres kun når intet snapshot findes endnu (ny kunde før første sweep)\neller ved ?refresh=1 — og resultatet persisteres, så næste visning er\nøjeblikkelig. Trustpilot-pending-kontrakten er uændret: status=\"pending\"\n+ task_id → kald igen med ?tp_task_id= (GBP genbruges fra snapshottet).","operationId":"reputation_check_api_reputation__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"tp_task_id","in":"query","required":false,"schema":{"type":"string","description":"Continue a pending Trustpilot task","default":"","title":"Tp Task Id"},"description":"Continue a pending Trustpilot task"},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force a fresh (paid, ~60s) live check","default":false,"title":"Refresh"},"description":"Force a fresh (paid, ~60s) live check"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/multi/{customer_id}":{"get":{"summary":"Multi Platform Check","description":"Multi-platform GEO check — Perplexity + ChatGPT + Gemini.","operationId":"multi_platform_check_multi__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nordic-visibility-index/{customer_id}":{"get":{"summary":"Nordic Visibility Index","description":"Nordic GEO Visibility Index — aggregated multi-platform + citations + competitor score.\n\nCombines three weighted components into a single Nordic GEO Visibility Index (0-100):\n  - Multi-platform AI visibility (ChatGPT + Gemini + Perplexity) — 40%\n  - Nordic local citations (krak.dk, dineo.dk, gulesider.no, hitta.se, eniro.se, trustpilot.com) — 35%\n  - Competitor relative standing — 25%\n\nReturns combined index with letter grade, breakdown, and actionable Nordic-specific recommendations.","operationId":"nordic_visibility_index_nordic_visibility_index__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nordic-visibility-check":{"get":{"summary":"Nordic Visibility Check","description":"Public Nordic GEO Visibility Check — domain-based, no signup needed.\n\nCombines GEO Score (15 dimensions) + Nordic local citation presence\ninto a single Nordic-weighted visibility index (0-100).\n\nNo international GEO tool offers Nordic-weighted visibility scoring\nfor free. This is a lead-generation tool that shows Danish/Nordic SMBs\ntheir Nordic-specific AI visibility instantly.\n\nFor a full analysis with multi-platform AI visibility, competitor\ncomparison, and weekly monitoring, sign up at geoa.app.","operationId":"nordic_visibility_check_nordic_visibility_check_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","description":"Domain to check (e.g. 'danskebank.dk')","title":"Domain"},"description":"Domain to check (e.g. 'danskebank.dk')"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-compare":{"get":{"summary":"Geo Compare","description":"Public GEO Score Comparison — domain-to-domain, no signup needed.\n\nCompares two domains' GEO Scores (15 dimensions, 0-100) side-by-side.\nIf no competitor is specified, returns the single domain's GEO Score.\n\nNo international GEO tool offers free domain-to-domain GEO comparison\nfor Nordic markets. This is a lead-generation tool that shows Danish/Nordic\nSMBs where they stand vs competitors.\n\nFor a full analysis with multi-platform AI visibility, competitor\ncomparison, and weekly monitoring, sign up at geoa.app.","operationId":"geo_compare_geo_compare_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","description":"Your domain to check (e.g. 'danskebank.dk')","title":"Domain"},"description":"Your domain to check (e.g. 'danskebank.dk')"},{"name":"competitor","in":"query","required":false,"schema":{"type":"string","description":"Optional competitor domain to compare against","default":"","title":"Competitor"},"description":"Optional competitor domain to compare against"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/citation/{customer_id}":{"get":{"summary":"Citation Rate Check","description":"Citation Rate + SOV analysis — cached + background refresh.\n\nReturns pre-computed data <2s when available.\nReturns explicit {\"status\":\"pending\"} imens Perplexity kører.\nBevarer {result: {...}} shape — Proof SEO / Shopify app læser den.","operationId":"citation_rate_check_citation__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/citation/trigger/{customer_id}":{"post":{"summary":"Citation Trigger Sync","description":"Sync citation trigger — kører live Perplexity-queries og returnerer resultat.\n\nBrugt af Proof SEO / Claude Code til manuelt at trigge + vente på\ncitation-analysen. Returnerer {result: citation_rate, sov, per_query}.\nKan tage 2-3 min pga Perplexity-hastighed.","operationId":"citation_trigger_sync_citation_trigger__customer_id__post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nordic-citations/check":{"get":{"summary":"Nordic Citation Direct","description":"Direct Nordic citations check — no customer lookup needed.\n\nUseful for testing and for pre-onboarding assessments.","operationId":"nordic_citation_direct_nordic_citations_check_get","parameters":[{"name":"company","in":"query","required":true,"schema":{"type":"string","title":"Company"}},{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nordic-citations/{customer_id}":{"get":{"summary":"Nordic Citation Check","description":"Check brand presence on Nordic business directories (krak.dk, dineo.dk, gulesider.no, etc.)\n\nNo international GEO tool tracks Nordic local directories — this fills that gap.\nReturns a citation score and per-directory breakdown.","operationId":"nordic_citation_check_nordic_citations__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/progress/{customer_id}":{"get":{"summary":"Customer Progress","description":"Weekly progress timeline for a customer.","operationId":"customer_progress_progress__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/progress":{"get":{"summary":"All Progress","description":"Progress overview for all customers — operatør-rute.\n\nStod åben indtil 2026-09-14 og udleverede de aktive kunders id, domæne og\nscore til enhver uden nøgle.","operationId":"all_progress_progress_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/customers":{"get":{"summary":"Dashboard Json","operationId":"dashboard_json_admin_customers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/setup-status":{"get":{"summary":"Admin Setup Status","description":"Hvem venter på opsætning — samme beregning som nattens digest.\n\nFindes, fordi nattens mail svarer på spørgsmålet én gang i døgnet, og\nspørgsmålet \"hvem skal jeg sætte op nu?\" stilles på andre tidspunkter.\nÉt regelsæt, to flader (setup_digest.build_setup_report) — aldrig en\nparallel optælling.","operationId":"admin_setup_status_admin_setup_status_get","parameters":[{"name":"include_own","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Own"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/setup-digest/run":{"post":{"summary":"Admin Setup Digest Run","description":"Send opsætnings-digestet nu (manuel kørsel). Skriver IKKE til\nscheduler_runs — en manuel kørsel må ikke lukke nattens periode.","operationId":"admin_setup_digest_run_admin_setup_digest_run_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/pipeline/run":{"post":{"summary":"Trigger Pipeline","operationId":"trigger_pipeline_pipeline_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pipeline/status":{"get":{"summary":"Pipeline Status","operationId":"pipeline_status_pipeline_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/recompute":{"post":{"summary":"Recompute All Scores","description":"Recompute visibility scores for all active customers and update stored content.\nUse this after fixing the visibility pipeline to refresh 0% scores.","operationId":"recompute_all_scores_recompute_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/customers/{customer_id}/rescrape":{"post":{"summary":"Rescrape Customer","description":"Re-scrape a customer's website and update their profile.\nUse when scraper has been improved and old profiles have garbage data.","operationId":"rescrape_customer_customers__customer_id__rescrape_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customers/{customer_id}/rescan":{"post":{"summary":"Rescan Customer Dimension","description":"Re-check a single GEO Score dimension for a customer's domain.\n\nUsed by the frontend's \"Verificér\" button: after the user fixes an issue,\nonly the affected dimension is re-run (fast, quota-friendly). The stored\nlatest GEO Score is updated in place and the overall score recalculated.\n\nQuery params:\n    dimension: one of the 15 dimension ids (e.g. \"llms_txt\", \"structured_data\").","operationId":"rescan_customer_dimension_customers__customer_id__rescan_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"dimension","in":"query","required":false,"schema":{"type":"string","default":"","title":"Dimension"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/indexnow-submit":{"post":{"summary":"Indexnow Submit","description":"Submit URLs to IndexNow (Bing m.fl.) — HERMES-protected admin endpoint.\n\nBody (optional): {\"urls\": [\"https://geoa.app/...\"]}.\nWithout a body, submits ALL URLs from the live sitemap (bruges efter deploys\nmed indholdsændringer, så Bing/Copilot ser opdateringerne med det samme).","operationId":"indexnow_submit_admin_indexnow_submit_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/serp-tracking/run":{"post":{"summary":"Serp Tracking Run","description":"Start SERP rank-tracking for geoa.app i baggrunden (ellers automatisk 07:30).\n\nKørslen tager ~4 min (36 DataForSEO-opslag), så den startes som baggrunds-\ntask og status følges i GET /admin/serp-tracking + scheduler_runs.\nCooldown: afvises hvis der allerede er data for i dag (upsert er idempotent,\nmen DataForSEO fakturerer pr. opslag) — override med ?force=true.","operationId":"serp_tracking_run_admin_serp_tracking_run_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/delta-email/run":{"post":{"summary":"Delta Email Run","description":"Kør den ugentlige delta-email manuelt (ellers automatisk efter weekly pipeline).\n\n- ?customer_id=<uuid> → kun én kunde; uden → hele sweep'et.\n- ?dry_run=true (default) → beregner delta + emailtekst uden at sende.\n- ?force=true → ignorér ugens idempotens-lås og \"intet nyt\"-skip.\nIdempotens: max én email pr. kunde pr. ISO-uge via delta_email_log.","operationId":"delta_email_run_admin_delta_email_run_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/crawler-visibility":{"get":{"summary":"Customer Crawler Visibility","description":"AI-crawler-hits på kundens EGET site (HERMES; geoa-connect fase 1).\n\nDashboard-kortet \"AI crawlers on your site\": geo-platform henter rollups\nfra geoa-connect (server-til-server, CONNECT_API_KEY — HERMES-nøglen\nforlader ALDRIG denne proces) og plan-gater svaret: Free = 7 dages\nhistorik + teaser med 90d-total, betalt = fuld 7/30/90-serie.\n\nFail-open: connect nede/ukonfigureret → {\"available\": false} med 200 —\naldrig 500, kortet skjuler sig så. Ingen events endnu →\n{\"connected\": false} → lærende tom-tilstand i frontend. Kontrakt:\ndocs/overlevering-lovable-crawler-visibility-2026-08-24.md.","operationId":"customer_crawler_visibility_api_customers__customer_id__crawler_visibility_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/engine-changelog":{"get":{"summary":"Engine Changelog Admin","description":"Engine-changelog (HERMES): motorernes citationsadfærd uge for uge over\nalle kunders nat-scan + de parrede uge-til-uge-skift. Hver række bærer\nsine nævnere (answers, recommended_measured, citations_total, paired_n).\n`source` filtrerer kilde ('scan' | 'grounded_competitor' | …); tom = alle.\nLæses sammen med \"[engine-changelog] MONITOR\"-linjerne i Railway-logs.","operationId":"engine_changelog_admin_admin_engine_changelog_get","parameters":[{"name":"weeks","in":"query","required":false,"schema":{"type":"integer","default":8,"title":"Weeks"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"","title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/engine-changelog/run":{"post":{"summary":"Engine Changelog Run","description":"Genberegn én uge (ISO-dato inden for ugen, fx 2026-08-10) eller seneste\nafsluttede uge. Kører i baggrunden — Cloudflare dræber >100 s — og\nskriver resultatet til Railway-loggen. Idempotent (upsert).","operationId":"engine_changelog_run_admin_engine_changelog_run_post","parameters":[{"name":"week","in":"query","required":false,"schema":{"type":"string","default":"","title":"Week"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/engine-changelog/market":{"get":{"summary":"Engine Changelog Market","description":"Engine-run-diff (HERMES): motorernes kilder pr. branche × land,\nmåledag for måledag (market_ai_answers), parret diff mod forrige måledag.\n'*'-rækker = landet på tværs af brancher målt samme dag. Hver række bærer\nanswers/citations_total/paired_n og `citations_capped` (chatgpt/claude/\ngemini: max 5 citationer pr. svar i market-scannet).","operationId":"engine_changelog_market_admin_engine_changelog_market_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"","title":"Country"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/engine-changelog/market/run":{"post":{"summary":"Engine Changelog Market Run","description":"Genberegn run-diffen for et land (alle brancher → også '*'-rækken) eller\nén branche. Baggrund (sporet task), idempotent upsert. Koster intet —\nlæser kun market_ai_answers.","operationId":"engine_changelog_market_run_admin_engine_changelog_market_run_post","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"industry","in":"query","required":false,"schema":{"type":"string","default":"","title":"Industry"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/community-signals/probe":{"get":{"summary":"Community Signals Probe","description":"Live felt-verifikation for bølge 1-kilderne (HERMES) — registry-\nkodetabel-reglen: datamodellen er først verificeret når proben har kørt\ngrønt mod LIVE API med Martins nøgler. `source` = ad_library | reddit |\nyoutube; `q` = søgeterm (typisk et kendt brand).","operationId":"community_signals_probe_admin_community_signals_probe_get","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"ad_library","title":"Source"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"DK","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recommended-monitor":{"get":{"summary":"Recommended Monitor","description":"C2 monitor-flade: fordelingen af de TRE udfald pr. kunde (HERMES).\n\nOmtalt · citeret som kilde · anbefalet — hver med SIN nævner (og\n'unmeasured' for anbefalet: NULL-rækker er umålte, aldrig nej). Feltet\nvises IKKE for kunder før monitor-perioden er ovre (A2 kobler fladerne\npå). Læses sammen med \"[c2] MONITOR\"-linjerne i Railway-logs.","operationId":"recommended_monitor_admin_recommended_monitor_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":14,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/serp-tracking":{"get":{"summary":"Serp Tracking Status","description":"Seneste placeringer + historik for de trackede geoa.app-søgeord.","operationId":"serp_tracking_status_admin_serp_tracking_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/search-ownership":{"get":{"summary":"Search Ownership","description":"Search Ownership Engine-datakilde: query↔side fra GSC API'et.\n\nv2 af ownership-kortet (docs/search-ownership-map-2026-07.md) — CSV-\neksporten kan ikke koble query↔side; denne rute kan. Klassificerer hver\nquery i intent-lag og splitter i SEO-datasæt (uden bot-støj) og GEO-\ndatasæt (kun LLM-formede queries). Kræver at kunden har GSC forbundet.","operationId":"search_ownership_admin_search_ownership_get","parameters":[{"name":"customer_id","in":"query","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/blog-coverage/run":{"post":{"summary":"Blog Coverage Run","description":"Start AI-coverage for vores egne blogindlæg nu (ellers den daglige tick 08:00).\n\nMåler om AI faktisk CITERER indlæggene — den eneste rigtige succesmetrik\nfor data-content. `slug` kører ét enkelt indlæg; uden den køres alle\n(~118 kald ≈ $0,71).\n\nStartes i BAGGRUNDEN og svarer med det samme. Grunden er målt, ikke\nteoretisk (2026-08-03): endpointet ventede før på kørslen, men ét enkelt\nindlæg tager ~2½ minut og hele sweepet flere gange dét — langt over\nCloudflares ~100-sekunders grænse. Gennem geoa.app gav det HTTP 524, og\nfordi uvicorn aflyser handleren når klienten forsvinder, blev kørslen\ndræbt uden at skrive sin række. Knappen kunne altså ikke bruges til det\nden var lavet til. Følg resultatet med GET /admin/blog-coverage.\n\nNB: en FULD manuel kørsel tæller som MÅNEDENS måling (job_name\n`blog_coverage`) og får derfor perioden-porten til at springe den\nplanlagte kørsel over — inklusive redaktions-mailen, som kun sendes af\ndet planlagte job. Et enkelt indlæg logges som `blog_coverage_spot` og\nrører ikke måneden.\n\n`engines` er den eksplicitte opt-in til multi-motor-undersøgelsen, fx\n`?engines=perplexity,chatgpt,claude,gemini`. Standard er Perplexity\nalene: de tre øvrige kører grounded, hvor hvert kald bærer et\nwebsøgnings-gebyr (~$10 brændt på én dags kørsler 2026-08-05), og de har\nallerede svaret — 14/17 nul på ChatGPT, 15/17 på Claude. Det er et\nengangs-fund, ikke en fast post på regningen.","operationId":"blog_coverage_run_admin_blog_coverage_run_post","parameters":[{"name":"slug","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"}},{"name":"engines","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Engines"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/gemini-probe":{"get":{"summary":"Gemini Grounding Probe","description":"Ét grounded Gemini-kald DIREKTE til Google — viser den faktiske svarform.\n\nFindes fordi feltnavne ikke gættes i dette repo: Googles grounding-side\ndokumenterer deres nye Interactions-API, ikke det `generateContent` vi\nkalder. `raw_shape` fortæller hvilken gren i `_gemini_citations` der\nfaktisk bar frugt — når prod har svaret, kan den anden slettes, og\nGEMINI_NATIVE_GROUNDING tændes.\n\nKoster ét kald (gratis inden for Googles 1.500/dag).","operationId":"gemini_grounding_probe_admin_gemini_probe_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"Hvad er GEO i markedsføring?","title":"Q"}},{"name":"model","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model"}},{"name":"list_models","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"List Models"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/grounded-competitor-scan":{"post":{"summary":"Grounded Competitor Scan Now","description":"Manuel kørsel af det ugentlige groundede konkurrent-scan (ops/verify).\n\nLogger kørslen i scheduler_runs som jobbet selv — så ugeporten ser ugen\nsom lukket og natjobbet IKKE kører dobbelt (kørslen koster betalte\nwebsøgningskald). Koster ~10 kald pr. entitled kunde med konkurrenter.","operationId":"grounded_competitor_scan_now_admin_grounded_competitor_scan_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/vendor-credits":{"get":{"summary":"Vendor Credits Status","description":"OpenRouter-saldo NU. `?alert=true` kører også dedup + mail som jobbet.\n\n`level` er ét af: ok · low · empty · unknown · disabled.\n**`unknown` er ikke `ok`** — det betyder, at vi ikke kunne tjekke, og det\nmå aldrig læses som \"saldoen er fin\".\n\nSaldoen i kroner og ører kræver en MANAGEMENT-nøgle\n(`OPENROUTER_MANAGEMENT_KEY`); uden den falder tjekket tilbage på en\nprobe, der kun kan svare \"kontoen svarer\" / \"kontoen er tom\".","operationId":"vendor_credits_status_admin_vendor_credits_get","parameters":[{"name":"alert","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Alert"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/blog-coverage":{"get":{"summary":"Blog Coverage Status","description":"Seneste coverage-kørsel: snit pr. indlæg, og separat for data-artiklerne.\n\n`data_posts_avg_score` er den vigtigste linje — den svarer på, om\noriginal-data-strategien rent faktisk giver citationer. Derfor kommer den\nKUN fra en REN kørsel (2026-08-25): den 5. august faldt ChatGPT, Claude\nog Gemini ud på 22 af 39 indlæg, fordi OpenRouter-kontoen var tom, og\ntallet 57,9 var i virkeligheden et Perplexity-only-tal. Det stod her som\nsvaret i 20 dage. Et tal fra en halvblind kørsel er værre end intet tal,\nså feltet er hellere `null` — og `engine_degraded` fortæller hvorfor.","operationId":"blog_coverage_status_admin_blog_coverage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/market-series/run":{"post":{"summary":"Market Series Run","description":"Kør kvartals-serien nu (ellers automatisk dag 3 i kvartalets 1. måned).\n\nRe-scanner det eksisterende panel (egne fetches, nul vendor-kost) og\nfryser aggregaterne som snapshot. `rescan=false` springer re-scan over og\nsnapshotter blot nuværende tal (brug den til at etablere baseline fra\njuli-scanningen). Idempotent pr. kvartal; `force=true` overstyrer.","operationId":"market_series_run_admin_market_series_run_post","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}},{"name":"rescan","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Rescan"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/dr-export":{"get":{"summary":"Dr Export","description":"Disaster-recovery-eksport af voldgravs-dataene (2026-07-23).\n\nTidsserierne er forretningens eneste ukopiérbare aktiv — og de boede kun\ni Supabase. Dette endpoint dumper dem som JSON til offsite-opbevaring\n(runbook: docs/RUNBOOKS.md — kør månedligt, gem filen udenfor Supabase).\nHERMES-guarded. `table` afgrænser (panel|snapshots|scans|all).","operationId":"dr_export_admin_dr_export_get","parameters":[{"name":"table","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Table"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/market-series":{"get":{"summary":"Market Series Status","description":"Tidsserien bag GEO-barometret: alle kvartals-snapshots + deltaer.\n\nHvert snapshot bærer metode-stemplet (n, panel, kvartal) så tallene kan\nciteres i data-artikler med \"af de samme N sites…\"-formuleringen.","operationId":"market_series_status_admin_market_series_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/gate/verify":{"post":{"summary":"Gate Verify","description":"Porten som fritstående værktøj: verificér ÉN påstand mod sin kilde.\n\nDeterministisk (intet tal uden kilde, kilde uden tallet, cirkulær kilde) +\net semantisk LLM-lag der fanger Trustpilot-fælden (tallet står på siden,\nmen understøtter ikke påstanden). Fail-open uden LLM-nøgle → mekanisk verdikt.\n\nDette er wedge'en i MCP-connector-planen OG den Port content.geoa.app skal\nbruge — bygget én gang, brugt begge steder. verdict ∈ {supported,\nunsupported, self_referential, unverifiable}.","operationId":"gate_verify_api_gate_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GateVerify"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/content-gate/check":{"post":{"summary":"Content Gate Check","description":"Content-porten (trin 04): verificér hvert tal i et udkast mod dets kilde.\n\nDeterministisk, ingen AI-kald. Henter faktisk de citerede sider. Returnerer\nen dom pr. tal: own_data / supported / unsupported / unsourced / circular /\nunreachable. Monitor-først: `passed` er kun false, hvis CONTENT_GATE_ENFORCE\ner sat OG mindst ét tal blokerer.","operationId":"content_gate_check_admin_content_gate_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentGateCheck"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/serp-tracking":{"get":{"summary":"Customer Serp Status","description":"Per-kunde SERP-tracking: seneste placeringer + uge-historik pr. søgeord.\n\nData opsamles automatisk hver mandag 08:30 for aktive kunder med geo_plan\n(kvote: starter 5 / pro 10 / business 20 søgeord). Felterne pr. søgeord:\nrank, ai_overview (udløser søgeordet en AI Overview), cites_customer\n(er kunden citeret i den), top_domains (ugens top-3).\n\nAdditivt 2026-09-06 (lokal måling): `measured_in` (byen målingen er\ntaget i, null = hele landet) og pr. søgeord `search_location`,\n`local_pack_rank` (kundens plads i Googles kortboks, null = ikke i den)\nog `local_pack` (boksens top-3: title/domain/rating/reviews; null =\ningen kortboks for søgeordet). Overlevering:\ndocs/overlevering-lovable-lokal-maaling-2026-09-06.md.","operationId":"customer_serp_status_api_customers__customer_id__serp_tracking_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/serp-tracking/keywords":{"put":{"summary":"Customer Serp Pin Keywords","description":"Kundens egne søgeord til rank-tracking — ERSTATTER hele listen.\n\nRækkefølgen er kundens (den første er vigtigst). Listen står forrest ved\nnæste måling og tæller inden for `keyword_quota`; resten af kvoten\nfyldes stadig af geoas eget valg. Tom liste = ingen fastgjorte søgeord.\nFlere end kvoten afvises (422 `too_many_keywords`) — hellere et klart\nnej end en stille beskæring af kundens valg.\n\nSvar: {pinned_keywords, keyword_quota, free_slots}. Målingen kører\nmandag, eller straks via POST .../serp-tracking/run.","operationId":"customer_serp_pin_keywords_api_customers__customer_id__serp_tracking_keywords_put","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SerpPinnedKeywords"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/serp-tracking/run":{"post":{"summary":"Customer Serp Run","description":"Start SERP-tracking for én kunde i baggrunden (ellers automatisk mandag).\n\nCooldown: afvises hvis kunden allerede har målinger for i dag (DataForSEO\nfakturerer pr. opslag) — override med ?force=true. force tillader også\ntracking af en kunde uden betalende plan (admin-test, starter-kvote).","operationId":"customer_serp_run_api_customers__customer_id__serp_tracking_run_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/alerts/cleanup":{"post":{"summary":"Admin Alerts Cleanup","description":"Ryd op i alert-historikken: kollaps gentagne alerts og ret gamle\nplatform-stavemåder (\"Chatgpt\" → \"ChatGPT\").\n\nBaggrund (G-041, 2026-08-31): sov_drop-detektoren sammenlignede 30 dage\nmod 60 dage — og de 60 INDEHOLDT de 30, så samme fald blev genfundet og\ngenudsendt hver nat. Koden er rettet, men de allerede afsendte dubletter\nstår stadig i kundernes indbakke. Fixet er først leveret, når indbakken\nogså er ren.\n\nDubletter slettes ikke, de sættes `is_dismissed=true` — så forsvinder de\nfra klokken uden at kundens historik forsvinder.\n\n    POST /admin/alerts/cleanup                       → dry-run, alle kunder\n    POST /admin/alerts/cleanup?customer_id=<uuid>    → dry-run, én kunde\n    POST /admin/alerts/cleanup?write=true            → skriver","operationId":"admin_alerts_cleanup_admin_alerts_cleanup_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"write","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Write"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/cleanup-duplicates":{"post":{"summary":"Cleanup Duplicates","description":"Remove duplicate customers (same domain). Keeps the one with most content history.","operationId":"cleanup_duplicates_admin_cleanup_duplicates_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/customers/{customer_id}/schema-markup":{"get":{"summary":"Customer Schema Markup","description":"Registerverificeret JSON-LD schema-markup for en kunde.\n\nBygger arketype-styret JSON-LD (LocalBusiness / ProfessionalService /\nOrganization+SoftwareApplication) hvor virksomhedsfelterne kommer fra\nverificerede registerdata (CVR/Brønnøysund/Bolagsverket/PRH) via Nordic\nData. Cache-first: bruger enrichment gemt på kunden; kun ét API-opslag\nhvis kunden aldrig er beriget (persisteres). Fail-open: uden registerdata\n(eller for ikke-nordiske kunder) genereres schema fra eksisterende data\nmed verified=false. `verified_fields` + `verification.label` lader\nfrontend vise \"CVR-verificeret\" som trust-signal.\n\n`?lang=da|no|sv|en` (valgfrit) styrer kun `instructions`-teksten; uden den\nfølges kundens `country_code` (tom → dansk). JSON-LD'en er uændret.","operationId":"customer_schema_markup_api_customers__customer_id__schema_markup_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/schema/{customer_id}":{"get":{"summary":"Schema Check","description":"Check website schema markup + Wikidata/Knowledge Graph for a customer.\n\nServerer dashboardets Lokal SEO-panel. `result` gaar gennem\n`first_finding.localize_schema_result` foer det sendes: schema_checker er\nudvikler-vendt og hardkodet dansk, og dens `missing_schemas[].description`\n+ `recommendations[].message` bliver renderet ORDRET i et dashboard der\nfindes paa fire sprog. Laget oversaetter via samme typetabel som\nWP-fundet, prioriterer efter kundens arketype i stedet for\nschema_checker's faste LocalBusiness-foerst-raekkefoelge, og fjerner\nJSON-LD-skabelonen med pladsholdere. Samme fejlklasse som\nfirst-finding-P0'en 2026-08-30, fundet paa naboefelterne.\n\n`?lang=da|no|sv|en` (valgfrit); uden den foelges kundens `country_code`\n(tom -> dansk, jf. `fix_i18n.fix_lang`). `summary` er Hermes-/udvikler-vendt\nmarkdown og roeres ikke — ingen frontend renderer den.","operationId":"schema_check_schema__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms-txt/generate":{"post":{"summary":"Llms Txt Generate","description":"Generate an /llms.txt and /llms-full.txt for a domain.\n\nFollows the llmstxt.org spec: H1, blockquote, sections, file lists.\nCan be called with raw domain or with pre-filled profile data.\nWith `customer_id`, kuraterede sameAs-links fra entity-setup medtages.","operationId":"llms_txt_generate_llms_txt_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LLMSTXTRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms-txt/generate/{customer_id}":{"post":{"summary":"Llms Txt Generate For Customer","description":"Generate llms.txt for a specific customer by ID.\n\nFetches customer data from Supabase, scrapes their domain,\ngenerates both files, and saves to llms_txt_configs table.","operationId":"llms_txt_generate_for_customer_llms_txt_generate__customer_id__post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms-txt/validate":{"get":{"summary":"Llms Txt Validate","description":"Validate an existing llms.txt at a domain against the spec.\n\nChecks if /llms.txt exists and validates its structure.\nReturns validation score + issues.","operationId":"llms_txt_validate_llms_txt_validate_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms-txt/{customer_id}":{"get":{"summary":"Llms Txt Get","description":"Get previously generated llms.txt for a customer.","operationId":"llms_txt_get_llms_txt__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/batch-visibility/start":{"post":{"summary":"Batch Visibility Start","description":"Start a batch visibility scan for a customer.\n\nBuilds prompts, uploads to OpenAI Batch API (50% cheaper),\nand saves batch job reference to Supabase.\nResults are ready within 24h — poll with GET /batch-visibility/status/{batch_id}.","operationId":"batch_visibility_start_batch_visibility_start_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchScanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/batch-visibility/status/{batch_external_id}":{"get":{"summary":"Batch Visibility Status","description":"Check status of a batch job and download results if complete.","operationId":"batch_visibility_status_batch_visibility_status__batch_external_id__get","parameters":[{"name":"batch_external_id","in":"path","required":true,"schema":{"type":"string","title":"Batch External Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/batch-visibility/results/{customer_id}":{"get":{"summary":"Batch Visibility Results","description":"Get recent batch scan results for a customer.\n\nAdditivt felt `kind_summary` (migration-055): aided/unaided-split over de\nreturnerede rækker. aided = spørgsmål der selv nævner brandet (AI'en\nekkoer navnet fra spørgsmålet), unaided = alt andet (organisk omtale).\nSplittet afgøres af `is_aided` (se nedenfor), ikke af `prompt_kind` alene\n— indtil 2026-09-18 faldt hver række uden `prompt_kind` UD af både\ngrupper, og det hul var dér forsidens site-liste regnede sit eget,\ninflaterede tal. `untagged` er nu et data-kvalitets-tal: hvor stor en del\naf sættet kolonnen er tom på.\n\nAdditivt felt `prompt_tracked` pr. række + `tracked_summary` (2026-09-18).\nFladen regner sit synligheds-tal som \"seneste svar pr. SPØRGSMÅL\" —\n`mentionRate(latestPerPrompt(rows))`. Uden at vide hvilke spørgsmål der\nstadig følges, tæller den også spørgsmål kunden har slettet, og et gammelt\n\"nævnt\" bliver stående i tælleren for altid. Målt på checkbiz.eu 18/9:\ndashboardet viste 4 af 19 = 21 %, mens kundens aktuelle sæt (15 spørgsmål,\nskiftet 16/9) stod på 0 omtaler. Backenden kender det aktuelle sæt; det er\nher, det hører hjemme — ikke i tre frontends hver sit sted.\n\nAdditivt felt `is_aided` pr. række: nævnte SPØRGSMÅLET selv brandet? Så\nbeviser svaret ingenting, og det tæller ikke i `tracked_summary.mentions`\n(samme regel som `share_of_voice`). `prompt_kind` alene rækker ikke —\nkolonnen er NULL på alt uden om batch-vejen.","operationId":"batch_visibility_results_batch_visibility_results__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mentions/analyze":{"post":{"summary":"Mention Analyze","description":"Analyze a single AI response for brand mentions and sentiment.\n\nTwo-tier: regex detection (free) + cheap LLM sentiment (only if brand is found).","operationId":"mention_analyze_mentions_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MentionAnalyzeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/share-of-voice/{customer_id}":{"get":{"summary":"Share Of Voice","description":"Get AI Share of Voice metrics for a customer.\n\nReturns visibility % over time, trend, sentiment, and per-platform breakdown.\nThis is the 'Search Console for AI' — the key metric.","operationId":"share_of_voice_share_of_voice__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/share-of-voice/{customer_id}/summary":{"get":{"summary":"Share Of Voice Summary","description":"Get compact Share of Voice summary — suitable for dashboard cards.","operationId":"share_of_voice_summary_share_of_voice__customer_id__summary_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/prompts/generate/{customer_id}":{"post":{"summary":"Prompts Generate","description":"Generate semantic GEO prompts for a customer.\n\nUses combinatorial approach: persona × intent × constraint\n→ 125 unique prompts from one cheap LLM call.\nReturns top 50 by Danish quality score.","operationId":"prompts_generate_prompts_generate__customer_id__post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/prompts/generate":{"post":{"summary":"Prompts Generate For Topic","description":"Generate prompts for a topic without customer context.","operationId":"prompts_generate_for_topic_prompts_generate_post","parameters":[{"name":"core_topic","in":"query","required":true,"schema":{"type":"string","title":"Core Topic"}},{"name":"brand_name","in":"query","required":false,"schema":{"type":"string","default":"","title":"Brand Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/narrative-audit/{customer_id}":{"get":{"summary":"Narrative Audit","description":"Run a Narrative Audit for a customer.\n\nAnalyzes how AI platforms describe and recommend the brand.\nReturns insights like:\n  - 'AI associerer dit brand med X men overser Y'\n  - 'Sentiment er faldende — her er hvorfor'\n  - Platform-specific breakdowns","operationId":"narrative_audit_narrative_audit__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/llms-txt/webhook":{"post":{"summary":"Llms Txt Webhook","description":"Webhook: auto-regenerate llms.txt when content changes.\n\nCalled by Lovable Cloud or CMS when customer updates their website.\nRegenerates both llms.txt and llms-full.txt and saves to Supabase.","operationId":"llms_txt_webhook_llms_txt_webhook_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LLMSTXTWebhookRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/reviews/{customer_id}":{"get":{"summary":"Review Platforms Check","description":"Feature 2: Check review platform presence — Trustpilot, Bing Places, etc.","operationId":"review_platforms_check_reviews__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/competitor-insights/{customer_id}":{"get":{"summary":"Competitor Insights","description":"Deep-dive competitor analysis — strengths, weaknesses, AI visibility per competitor.","operationId":"competitor_insights_competitor_insights__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/competitor-comparison/{customer_id}":{"get":{"summary":"Competitor Comparison","description":"Side-by-side GEO Score comparison — customer vs competitors. 0 kr.","operationId":"competitor_comparison_competitor_comparison__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/content-rewrite":{"get":{"summary":"Content Rewrite","description":"GEO Content Rewrite Suggestions — analyseér indhold og få specifikke forbedringsforslag.\n\nBevidst offentlig: analysen kører på en URL, ikke på en kunde.\n\nQuery params:\n    url: Domain or URL to analyze (e.g. 'example.com')","operationId":"content_rewrite_content_rewrite_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/pricing":{"get":{"summary":"Pricing Page","description":"Simple pricing page for Geoa.","operationId":"pricing_page_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/content/from-youtube":{"post":{"summary":"Content From Youtube","description":"Fetch a YouTube transcript and generate blog + LinkedIn content from it.\n\nAccepts:\n    {\"youtube_url\": \"...\", \"customer_id\": \"...\"}\n\nFetches the customer's profile from Supabase, generates content based\non the transcript, stores it, and returns the result.","operationId":"content_from_youtube_api_content_from_youtube_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/YouTubeContentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feed-inspiration/{industry}":{"get":{"summary":"Feed Inspiration","description":"Get recent RSS feed articles for a Danish industry.\n\nArgs:\n    industry: Industry key (revisorer, vvs, advokater, it-konsulenter, generelt).\n    count:    Number of recent articles to return (default 5, max 20).\n\nReturns:\n    JSON with articles and optional HTML snippet for email embedding.","operationId":"feed_inspiration_api_feed_inspiration__industry__get","parameters":[{"name":"industry","in":"path","required":true,"schema":{"type":"string","title":"Industry"}},{"name":"count","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Count"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/news-inspiration/{industry}":{"get":{"summary":"News Inspiration","description":"Get recent articles from Google News for a Danish industry.\n\nMerges blogwatcher RSS feeds with Google News RSS (free, no API key).\nProvides broader content coverage than blogwatcher alone.\n\nArgs:\n    industry: Industry key (revisorer, vvs, advokater, it-konsulenter, generelt, ...).\n    count:    Number of recent articles to return (default 5, max 20).\n\nReturns:\n    JSON with merged articles and HTML snippet for email embedding.","operationId":"news_inspiration_api_news_inspiration__industry__get","parameters":[{"name":"industry","in":"path","required":true,"schema":{"type":"string","title":"Industry"}},{"name":"count","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Count"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/debug/pipeline/{customer_id}":{"get":{"summary":"Debug Pipeline Route","description":"4-phase systematic debug for a customer's pipeline.","operationId":"debug_pipeline_route_debug_pipeline__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-sfe/{customer_id}":{"get":{"summary":"Geo Sfe Audit","description":"GEO-SFE Website Audit — structural AI-readiness analysis.\n\nCache-first (migration-048): den live audit tager ~15-20s (Jina-fetch),\nhvilket er længere end klienternes timeouts (seo-proof: 10s). Derfor:\n  1. Frisk cache (<6t)  → servér direkte (<1s), + \"cached\": true.\n  2. Stale cache        → servér cached STRAKS + genberegn i baggrunden\n                          (stale-while-revalidate; + \"refreshing\": true).\n  3. Ingen cache        → kør auditten synkront (uændret adfærd/shape) —\n                          som asyncio-task, så et klient-afbrud (timeout)\n                          ikke annullerer beregningen: cachen skrives\n                          alligevel, og næste kald er <1s.\nSvar-shape er uændret; \"cached\"/\"cached_at\"/\"refreshing\" er additive.\n\nScans customer's website for:\n- Heading hierarchy (H1-H4 depth)\n- Text walls (>100 word paragraphs)\n- Table/list usage\n- BLUF (answer in first 60 words)\n- Factual density (numbers, %, prices, dates, citations)\n- AI crawler access (robots.txt blocking)\n- Revenue forecast from visibility gap","operationId":"geo_sfe_audit_geo_sfe__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sentiment/{customer_id}":{"get":{"summary":"Sentiment Analysis","description":"Analyze sentiment in AI responses about this customer.\n\nReturns positive/neutral/negative classification based on\nPerplexity and multi-platform visibility responses.","operationId":"sentiment_analysis_sentiment__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/review-velocity/{customer_id}":{"get":{"summary":"Review Velocity","description":"Review Velocity Tracker — analyze review platform growth over time.\n\nChecks current review platform presence, stores a snapshot, and\ncompares against historical snapshots to calculate velocity.","operationId":"review_velocity_review_velocity__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cannibalization/{customer_id}":{"get":{"summary":"Cannibalization Analysis","description":"Kannibalisering — hvilke af kundens PUBLICEREDE sider konkurrerer?\n\nMåler Search Console (samme søgning, flere af kundens sider) og ellers\nkundens egne sidetitler. Aldrig kladder. Ingen eksterne kald.","operationId":"cannibalization_analysis_cannibalization__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/internal-links/{customer_id}":{"get":{"summary":"Internal Linking Audit","description":"Internal Linking Optimizer — analyze a customer's website link structure.\n\nScrapes the customer's website via Jina.ai, maps internal links,\nidentifies orphan pages, and suggests anchor text improvements.\n\nUses Jina.ai (already available) — no additional costs.","operationId":"internal_linking_audit_internal_links__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/faq/{customer_id}":{"get":{"summary":"Faq Generation","description":"FAQ Generator — generates structured FAQ content from Perplexity AI.\n\nDiscovers what customers ask about this brand, structures answers as\nFAQ content with Schema.org FAQPage markup.\n\nCost: ~1 Perplexity API call (≈0,05 kr per gennemkørsel).","operationId":"faq_generation_faq__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/keyword-gaps/{customer_id}":{"get":{"summary":"Keyword Gap Analysis","description":"Keyword Gap Analysis — discover keywords competitors have that you don't.\n\nUses Brave Search (free, we already have the key) to find what\nkeywords/topics competitors rank for, compares with customer's existing\ncontent, and identifies specific gaps with content suggestions.\n\n**0 kr drift** — uses existing Brave API key.","operationId":"keyword_gap_analysis_keyword_gaps__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/content-brief":{"get":{"summary":"Content Brief Endpoint","description":"Content Brief Generator — analyzes SERP and generates a structured brief.\n\nUses Brave Search (free) to analyze top-ranking content for a topic,\nthen generates title options, outline, key points, and SEO recommendations.\n\nQuery params:\n    topic: The topic/keyword to research (required).\n    customer_id: Optional — personalisering; kræver adgang til kunden.\n    industry: Optional — industry context.\n\n**0 kr drift** — uses existing Brave API key.","operationId":"content_brief_endpoint_content_brief_get","parameters":[{"name":"topic","in":"query","required":true,"schema":{"type":"string","title":"Topic"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"industry","in":"query","required":false,"schema":{"type":"string","default":"","title":"Industry"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eeat/{customer_id}":{"get":{"summary":"Eeat Check","description":"E-E-A-T Checker — analyzes a customer's website for Google E-E-A-T signals.\n\nScans for signals across 4 categories:\n- Experience: case studies, testimonials, years, portfolio\n- Expertise: certifications, team, education, publications\n- Authoritativeness: media mentions, awards, partners\n- Trustworthiness: contact info, privacy policy, terms, pricing\n\nUses Jina.ai (already configured) — no extra cost.","operationId":"eeat_check_eeat__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/entity-builder/plan":{"get":{"summary":"Entity Builder Plan","description":"Entity Authority Builder plan — prioritized, copy-paste profile pack.\n\nv1 returns Geoa's own canonical entity data (single source of truth in\nentity_config). Consumed by the marketing /entity-builder page and, later,\nthe Lovable dashboard. The generator is entity-data-driven so it generalizes\nto per-customer packs.","operationId":"entity_builder_plan_api_entity_builder_plan_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Entity Builder Plan Custom","description":"Personalized Entity Authority Builder plan for a specific customer.\n\nLovable posts the customer's business context; the backend returns a\nprioritized, copy-paste profile pack. With `use_ai: true` (and an OpenRouter\nkey), the profile copy is AI-tailored to the customer's situation; otherwise\nit falls back to deterministic templates. Empty body → Geoa's own pack.\n\nIf `customer_id` is set, data persisted via\nPOST /api/customers/{id}/entity-setup is merged in fill-if-blank, so the\nprofile pack is personalized without the user re-typing their context.","operationId":"entity_builder_plan_custom_api_entity_builder_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityBuilderPlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/entity-builder/review":{"post":{"summary":"Entity Builder Review","description":"AI assesses + improves a customer's draft for a platform/forum.\n\nThe core \"attach AI\" surface Lovable wires into the builder: the customer\npastes their own draft (LinkedIn bio, G2 description, a forum reply, ...) and\ngets a GEO-aware score, feedback and a ready-to-paste improved version,\ntailored to their business. Requires an OpenRouter key (fail-open: returns\nok=false + error when unavailable so the frontend can degrade gracefully).","operationId":"entity_builder_review_api_entity_builder_review_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityBuilderReviewRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/entity-authority/check":{"get":{"summary":"Entity Authority Direct","description":"Direct Entity Authority check — no customer lookup, nothing persisted.\n\nFor testing and pre-onboarding assessments (mirrors /nordic-citations/check).","operationId":"entity_authority_direct_entity_authority_check_get","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"company","in":"query","required":false,"schema":{"type":"string","default":"","title":"Company"}},{"name":"city","in":"query","required":false,"schema":{"type":"string","default":"","title":"City"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/signal-status":{"get":{"summary":"Customer Signal Status","description":"Bølge 2 (2026-08-14): \"du mangler N af M signaler\"-kontrakten.\n\nAdditiv. M = aktive+målte signaler for kundens arketype/branche, N af dem\nmangler — beregnet af SAMME gating som fix-producerne, så tallet og de\nserverede fixes aldrig kan være uenige. Payload-eksempel i\ndocs/overlevering-lovable-boelge2-signaler-2026-08-14.md.","operationId":"customer_signal_status_api_customers__customer_id__signal_status_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/prompt-stability":{"get":{"summary":"Customer Prompt Stability","description":"Backlog-punkt 2 (2026-08-15): \"Nævnt i X af de seneste Y målinger\"\npr. prompt. AI-fladerne er flygtige — den stabile VISNING er andelen,\nikke seneste enkeltmåling (dødbåndsprincippet). Rå data urørt; additivt.\n\nstate: stable (≥80 % af ≥3 målinger) · volatile (midt imellem) ·\nabsent (0 af ≥3) · new (<3 målinger — for tidligt at kalde).","operationId":"customer_prompt_stability_api_customers__customer_id__prompt_stability_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":14,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/entity-authority/{customer_id}":{"get":{"summary":"Entity Authority Check","description":"Entity Authority report for a customer (score + delchecks + anbefalinger).\n\nReturns the stored report when fresh (< 7 days) unless ?refresh=true.\nEvery sub-check is fail-open: missing keys/data degrade to \"ikke_tjekket\".","operationId":"entity_authority_check_entity_authority__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/rankings-check":{"get":{"summary":"Check Single Rank","description":"One-off rank check for any keyword. Returns top 10 Google results.","operationId":"check_single_rank_rankings_check_get","parameters":[{"name":"keyword","in":"query","required":true,"schema":{"type":"string","title":"Keyword"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/rankings-keywords":{"get":{"summary":"Keyword Research","description":"Get search volume, CPC and competition for keywords.\n\nQuery params:\n    keywords: Comma-separated keywords.\n    country: Marked (dk/se/no/…). Default dk — bagudkompatibelt.\n\nLandet afgør location_code + sprog via den kanoniske serp_locale-mapper\n(samme leksikon som SERP-trackingen), så volumen for SE/NO ikke tavst\nmåles mod dansk Google. Google Ads' sprogkode for norsk er \"no\", mens\nvores prompt-leksikon bruger \"nb\" — derfor oversættelsen herunder.","operationId":"keyword_research_rankings_keywords_get","parameters":[{"name":"keywords","in":"query","required":true,"schema":{"type":"string","title":"Keywords"}},{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"dk","title":"Country"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/rankings-backlinks/{customer_id}":{"get":{"summary":"Customer Backlinks","description":"Backlink analysis for a customer's domain.\n\nUses DataForSEO Backlinks API (trial active until 07.07.2026).","operationId":"customer_backlinks_rankings_backlinks__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/rankings/{customer_id}":{"get":{"summary":"Customer Rankings","description":"SERP Rank Tracking — check Google rankings for a customer's keywords.\n\nUses DataForSEO SERP API (Live mode, ~$0.002/SERP).\nKeywords can be passed as comma-separated, or auto-detected from customer profile.\n\nQuery params:\n    keywords: Optional comma-separated keywords. If omitted, auto-detected.","operationId":"customer_rankings_rankings__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"keywords","in":"query","required":false,"schema":{"type":"string","default":"","title":"Keywords"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dashboard/{customer_id}":{"get":{"summary":"Unified Dashboard","description":"Unified dashboard endpoint — returns all customer data in one JSON.\n\nAggregates: GEO score, geo-sfe audit, rankings, E-E-A-T, cannibalization,\ncitation rate, report data, progress, and customer info.\n\nStale-while-revalidate: returnerer cached svar øjeblikkeligt og refresher\ni baggrunden — brugeren venter kun ved allerførste kold start.\nBypass med ?refresh=true (admin/force).\n\nVærn: HERMES-nøgle ELLER ejerens JWT. Bemærk at det offentlige\ndelingslink (`/api/shared-report/{token}`) kalder denne FUNKTION direkte\n— ikke ruten — og derfor er upåvirket af værnet.","operationId":"unified_dashboard_api_dashboard__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score":{"get":{"summary":"Geo Score Check","description":"Unified GEO Score — check any website's AI search engine readiness.\n\nBevidst OFFENTLIG (gratis-testen i tragten, rate-limited). `customer_id`\ner valgfri berigelse og gates af `_may_read_customer` — ikke af ruten.\nSelve scoringen koster ingen credits og kræver intet login.","operationId":"geo_score_check_geo_score_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"fresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Fresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score/share/{token}":{"get":{"summary":"Geo Score Share","description":"View a shared GEO Score report (read-only, no login required).\n\nThe token is the MD5 hash of the domain (first 12 chars).\n\nContent negotiation: browsers (Accept includes text/html) get the rendered\nHTML report page; programmatic callers (application/json or */*) keep the\noriginal JSON shape so existing frontend integrations don't break.\nLink-preview crawlers (LinkedIn/Facebook/Slack etc.) send Accept: */* but\nneed the HTML to read the Open Graph tags, so they are detected by UA.","operationId":"geo_score_share_geo_score_share__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score/share/{token}/og.png":{"get":{"summary":"Geo Score Share Og Image","description":"Dynamic Open Graph badge (PNG) for a shared GEO report.","operationId":"geo_score_share_og_image_geo_score_share__token__og_png_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/credits/{customer_id}":{"get":{"summary":"Get Credits","description":"Get remaining credits + tier info for a customer.\n\nVærn: HERMES-nøgle ELLER ejerens JWT.","operationId":"get_credits_api_credits__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/auth-url":{"get":{"summary":"Gsc Auth Url","description":"Return the Google OAuth URL for a customer (HERMES-protected).\n\nLovable calls this server-side, then redirects the user's browser to\nthe returned auth_url. The state is signed + short-lived, so the public\ncallback only accepts flows initiated through this endpoint.\n\nscope (additivt, 2026-08-30): \"gsc\" beder KUN om Search Console, \"ga4\"\nkun om Analytics. Tom = begge, som hidtil. Knappen \"Forbind Search\nConsole\" boer sende scope=gsc — ellers ender brugeren i Analytics'\nopsaetning uden at have bedt om den.","operationId":"gsc_auth_url_gsc_auth_url_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/auth-url":{"get":{"summary":"Ga4 Auth Url","description":"Google OAuth-URL der KUN beder om Analytics — GA4-kortets egen knap.\n\nFindes for at holde de to integrationer adskilt: Search Console-knappen\nmaa ikke forbinde Analytics, og Analytics-knappen maa ikke lade som om\nden satte Search Console op.","operationId":"ga4_auth_url_ga4_auth_url_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/auth":{"get":{"summary":"Gsc Auth","description":"Start Google Search Console OAuth flow (HERMES-protected).\nPrefer /gsc/auth-url from frontends — this redirect variant is for\nserver-side/manual use.","operationId":"gsc_auth_gsc_auth_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/sitemap-submit":{"get":{"summary":"Gsc Sitemap Submit","description":"Knappen på kortet `dim.sitemap.not_submitted` (2026-09-28).\n\nSamme kontrakt som `/bing/sitemap-submit`: offentlig bag et signeret\nlink, der kun udstedes, når GSC_SITEMAP_SUBMIT_LIVE er tændt. Afviser\nGoogle (læse-token), går kunden ÉN gang til Googles godkendelse af\n`webmasters` (inkrementelt), og callbacket indsender. Alle blindgyder\nender et sted, kunden kan handle: dashboardet eller Search Console.","operationId":"gsc_sitemap_submit_gsc_sitemap_submit_get","parameters":[{"name":"c","in":"query","required":false,"schema":{"type":"string","default":"","title":"C"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/callback":{"get":{"summary":"Gsc Callback","description":"Handle Google Search Console OAuth callback.\nVerifies the signed state, exchanges code for tokens + saves to\nSupabase, then sends the user back to the dashboard.","operationId":"gsc_callback_gsc_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","default":"","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/status/{customer_id}":{"get":{"summary":"Gsc Status","description":"Whether the customer has a GSC connection (for the 'Forbind GSC' card).\n\nproperty_missing (additive): connected, but the Google account has no\nSearch Console property for the customer's domain — the frontend must\nshow the create-property guidance instead of empty charts.","operationId":"gsc_status_gsc_status__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/recheck-property":{"post":{"summary":"Api Gsc Recheck Property","description":"Re-probe whether the connected Google account now has a GSC property\nfor the customer's domain (the 'Tjek igen' button after gsc=no_property).\nKicks off the backfill when the property has appeared.","operationId":"api_gsc_recheck_property_api_gsc_recheck_property_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bing/auth-url":{"get":{"summary":"Bing Auth Url","description":"Return the Bing Webmaster OAuth URL for a customer (HERMES-protected).\n\nLovable calls this server-side, then redirects the user's browser to\nthe returned auth_url. State is signed + short-lived (same scheme as GSC).","operationId":"bing_auth_url_bing_auth_url_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bing/auth":{"get":{"summary":"Bing Auth","description":"Start Bing Webmaster OAuth flow (HERMES-protected redirect variant).","operationId":"bing_auth_bing_auth_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bing/sitemap-submit":{"get":{"summary":"Bing Sitemap Submit","description":"Knappen på kortet `dim.sitemap.not_submitted_bing` (2026-09-26).\n\nOffentlig, men beskyttet af et signeret link, der kun kan udstedes af\nproduceren for netop denne kunde (`sitemap_submission.sign_bing_submit`).\nIndsender med den forbindelse, kunden har; afviser Bing (læse-token), går\nkunden ÉN gang til Microsofts godkendelse af `webmaster.manage`, og\ncallbacket indsender. Alle blindgyder ender et sted, kunden kan handle:\ndashboardet eller Bings egen sitemap-side.","operationId":"bing_sitemap_submit_bing_sitemap_submit_get","parameters":[{"name":"c","in":"query","required":false,"schema":{"type":"string","default":"","title":"C"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bing/callback":{"get":{"summary":"Bing Callback","description":"Handle Bing Webmaster OAuth callback (public — validated via signed state).","operationId":"bing_callback_bing_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","default":"","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/bing/status/{customer_id}":{"get":{"summary":"Bing Status","description":"Whether the customer has a Bing WMT connection (for the 'Forbind Bing' card).","operationId":"bing_status_bing_status__customer_id__get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/bing/sync-status":{"get":{"summary":"Api Bing Sync Status","description":"Bing 'backfill' status — same shape as /api/gsc/sync-status.\n\nBing has NO backfill phase: search/traffic stats are fetched live from\nthe Bing API, so data is available immediately after connect. This\nendpoint exists for UX parity with GSC — it reports 'completed' as soon\nas tokens exist, so frontends can reuse the GSC polling flow unchanged.","operationId":"api_bing_sync_status_api_bing_sync_status_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/bing/searchstats":{"get":{"summary":"Bing Searchstats","description":"Bing query-level search performance (HERMES-protected).","operationId":"bing_searchstats_api_bing_searchstats_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/bing/trafficstats":{"get":{"summary":"Bing Trafficstats","description":"Bing daily clicks/impressions timeseries (HERMES-protected).","operationId":"bing_trafficstats_api_bing_trafficstats_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/bing/select-site":{"post":{"summary":"Bing Select Site","description":"Bind a customer to one of their verified Bing Webmaster sites.\n\nUntil this existed, site binding was auto-only: /bing/callback matched\ncustomers.domain against the account's verified sites, and a miss left\nsite_url empty with no way back. The card said \"connected\", both tables\nsaid \"contact support\", and the customer was stuck (verified in prod\n2026-08-26). This is the manual way out.","operationId":"bing_select_site_api_bing_select_site_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BingSiteSelection"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/page-hygiene":{"get":{"summary":"Page Hygiene Read","description":"Per-side teknisk hygiejne (HERMES-beskyttet).\n\nLæser kun det gemte sweep-snapshot. `measured=false` betyder at siden ikke\nkunne hentes — den tælles aldrig som fejlbehæftet, og `clean_pct` er None\n(ikke 0) når intet er målt.","operationId":"page_hygiene_read_api_page_hygiene_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/page-hygiene/run":{"post":{"summary":"Page Hygiene Run","description":"Kør hygiejne-sweepet for én kunde nu (HERMES-beskyttet).","operationId":"page_hygiene_run_admin_page_hygiene_run_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/aio-impact":{"get":{"summary":"Aio Impact Read","description":"AI Overview-skatten på kundens egne søgeord (HERMES-beskyttet).\n\nJoiner den ugentlige SERP-måling (hvilke søgeord udløser en AI Overview)\nmed Search Console (hvad CTR er på de samme søgeord). Læser kun gemte\ndata — ingen betalte kald.\n\nSvaret bærer ALTID `comparison.comparable` + `method`. Er grupperne ikke\nsammenlignelige (for få søgeord/visninger, eller for stor forskel i\nplacering), er `ctr_gap_pct` None og `reason` forklarer hvorfor —\net CTR-tal uden sin forudsætning ville måle placering, ikke AI Overview.","operationId":"aio_impact_read_api_aio_impact_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/indexing-coverage":{"get":{"summary":"Indexing Coverage Read","description":"Seneste indekseringsstatus pr. side (HERMES-beskyttet).\n\nLæser kun det gemte snapshot — ingen kald til Google her. Har sweepet\naldrig kørt for kunden, er svaret tomt med checked_at=None; det er en\nærlig \"vi har ikke målt endnu\", ikke \"alt er indekseret\".\n\n`status` (2026-09-18) siger HVILKEN slags tomhed det er — `measured`,\n`unmeasured` (vi har sider, men kunne ikke måle dem) eller `never_swept`\n— og `reason`/`reason_code` siger hvorfor. Uden dem var alle tre det\nsamme svar af nuller, og fladen havde intet at forklare kunden med.","operationId":"indexing_coverage_read_api_indexing_coverage_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/indexing-coverage/run":{"post":{"summary":"Indexing Coverage Run","description":"Start sweepet for én kunde (HERMES-beskyttet). Svarer 202 med det samme.\n\nHVORFOR ASYNKRON (2026-09-22). Ruten kørte sweepet i selve requesten:\nop til 100 URL Inspection-kald à ~1 s plus 0,5 s throttle ≈ 2-3 minutter.\nDet overlever hverken proxyen foran Railway eller et deploy midt i\nkaldet — WonMetrics-rutinen fik 502 efter 57 s (22/9) og en tabt\nforbindelse efter 120 s (20/9), og vidste bagefter ikke, om målingen\nvar landet. Nu startes kørslen i baggrunden, og resultatet læses, hvor\ndet altid har boet: GET /api/indexing-coverage (`checked_at` rykker).\n\n`customer_id` må stå i query-strengen ELLER i en JSON-krop — rutinerne\nhar prøvet begge, og krop-varianten gav \"Manglende parameter\".","operationId":"indexing_coverage_run_admin_indexing_coverage_run_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/inspect":{"get":{"summary":"Gsc Inspect","description":"Inspect a URL via GSC URL Inspection API (HERMES-guarded).","operationId":"gsc_inspect_gsc_inspect_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/keywords":{"get":{"summary":"Gsc Keywords","description":"Get search keywords from Google Search Console (HERMES-guarded).","operationId":"gsc_keywords_gsc_keywords_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"start_date","in":"query","required":false,"schema":{"type":"string","default":"","title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"type":"string","default":"","title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/sync-status":{"get":{"summary":"Api Gsc Sync Status","description":"Backfill/sync state for the customer's stored GSC history.","operationId":"api_gsc_sync_status_api_gsc_sync_status_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/backfill":{"post":{"summary":"Api Gsc Backfill","description":"Trigger (or resume) the 16-month history backfill. Idempotent —\ncompleted backfills are not re-run unless force=true.","operationId":"api_gsc_backfill_api_gsc_backfill_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/summary":{"get":{"summary":"Api Gsc Summary","description":"Daily clicks/impressions/CTR/position series + period deltas\n(vs previous window and vs same window last year).","operationId":"api_gsc_summary_api_gsc_summary_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/queries":{"get":{"summary":"Api Gsc Queries","description":"Aggregated top queries from stored history, incl. explicit\n'(anonymized)' share row.","operationId":"api_gsc_queries_api_gsc_queries_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":250,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/matrix":{"get":{"summary":"Api Gsc Matrix","description":"Query -> page matrix from stored history (raw material for the\ndownstream recommendation engines).","operationId":"api_gsc_matrix_api_gsc_matrix_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":1000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/brand-terms":{"get":{"summary":"Api Gsc Brand Terms","description":"The customer's brand dictionary (auto-seeded from the profile).","operationId":"api_gsc_brand_terms_api_gsc_brand_terms_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Api Gsc Brand Terms Update","description":"Add/remove dictionary terms; brand classes are recomputed right away.","operationId":"api_gsc_brand_terms_update_api_gsc_brand_terms_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandTermsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Api Gsc Brand Terms Delete","description":"Remove a single dictionary term (same effect as POST with remove=[term]).\n\nAdded so the frontend's `DELETE /api/gsc/brand-terms?customer_id=&term=`\nwrapper works 1:1 — brand classes are recomputed right away.","operationId":"api_gsc_brand_terms_delete_api_gsc_brand_terms_delete","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"term","in":"query","required":false,"schema":{"type":"string","default":"","title":"Term"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/classify":{"post":{"summary":"Api Gsc Classify","description":"Classify all (new) queries in the customer's stored GSC data. Runs in\nthe background; results land in query_classifications.","operationId":"api_gsc_classify_api_gsc_classify_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/classification":{"get":{"summary":"Api Gsc Classification","description":"Brand/non-brand split + intent distribution for non-brand queries.","operationId":"api_gsc_classification_api_gsc_classification_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recommendations/generate":{"post":{"summary":"Api Recommendations Generate","description":"Run all detectors on the stored GSC data and refresh the queue.\n\n`days` er som udgangspunkt de 28 dage, kundens dashboard selv viser —\net tal i et råd skal kunne genfindes på den flade, rådet står på.\n\nConversion-value weighting is attempted automatically: if the customer's\nGA4 allocation passes the ~5% reconciliation gate, the queue is re-ranked\nby expected conversions instead of raw clicks. Fail-open — a broken GA4\nlayer never blocks recommendations.","operationId":"api_recommendations_generate_api_recommendations_generate_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}},{"name":"band_min","in":"query","required":false,"schema":{"type":"number","default":4.0,"title":"Band Min"}},{"name":"band_max","in":"query","required":false,"schema":{"type":"number","default":15.0,"title":"Band Max"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recommendations":{"get":{"summary":"Api Recommendations","description":"The customer's prioritized recommendation queue (persisted).\n\nAnbefalinger, som den seneste kørsel ikke har bekræftet, serveres IKKE —\n`stale_hidden` siger hvor mange, og `last_verified_at` hvornår køen sidst\nblev efterprøvet. `include_stale=true` henter dem alligevel (fejlsøgning).","operationId":"api_recommendations_api_recommendations_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"open","title":"Status"}},{"name":"rec_type","in":"query","required":false,"schema":{"type":"string","default":"","title":"Rec Type"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"include_stale","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Stale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recommendations/status":{"post":{"summary":"Api Recommendations Status","description":"Track progress: mark a recommendation open/done/dismissed.","operationId":"api_recommendations_status_api_recommendations_status_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecommendationStatusUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/visibility/combined":{"get":{"summary":"Api Visibility Combined","description":"ONE view: classic Google visibility + AI-scan mentions + Gen-AI\nimpressions per customer (\"nr. 7 i Google — ikke nævnt i ChatGPT\").","operationId":"api_visibility_combined_api_visibility_combined_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/genai-impressions":{"get":{"summary":"Api Gsc Genai Impressions","description":"Stored Gen-AI impressions series (where Google has rolled it out).","operationId":"api_gsc_genai_impressions_api_gsc_genai_impressions_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc/genai-sync":{"post":{"summary":"Api Gsc Genai Sync","description":"Manual Gen-AI sync (probe availability + pull impressions).","operationId":"api_gsc_genai_sync_api_gsc_genai_sync_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/conversions/queries":{"get":{"summary":"Api Conversions Queries","description":"GA4-konverteringer fordelt på søgeord (klik-proportional allokering,\nmed afstemning mod GA4's organic-kanal og eksplicit anonymiseret række).","operationId":"api_conversions_queries_api_conversions_queries_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ga4/sync":{"post":{"summary":"Api Ga4 Sync","description":"Manual GA4 fact-layer sync for one customer.","operationId":"api_ga4_sync_api_ga4_sync_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gsc-alerts/check":{"post":{"summary":"Api Gsc Alerts Check","description":"Run the season-aware GSC anomaly detectors for one customer now.\nFired alerts persist in the alerts table and are emailed via Resend.","operationId":"api_gsc_alerts_check_api_gsc_alerts_check_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reports/monthly":{"get":{"summary":"Api Monthly Report","description":"The monthly one-pager as versioned JSON (month=YYYY-MM, default:\nlast full month). Non-brand clicks are the primary metric.","operationId":"api_monthly_report_api_reports_monthly_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"month","in":"query","required":false,"schema":{"type":"string","default":"","title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reports/monthly/send":{"post":{"summary":"Api Monthly Report Send","description":"Build and email the monthly one-pager to the customer via Resend.","operationId":"api_monthly_report_send_api_reports_monthly_send_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"month","in":"query","required":false,"schema":{"type":"string","default":"","title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reports/monthly/history":{"get":{"summary":"Api Monthly Report History","description":"Sent/generated monthly-report history for a customer, newest first.","operationId":"api_monthly_report_history_api_reports_monthly_history_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":24,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/share-tokens":{"post":{"summary":"Create Report Share Token","description":"Mint a share token for a customer's report (HERMES-guarded).\n\nReturns the raw token ONCE — only its sha256 hash is stored. The public\nread side is GET /api/shared-report/{token}.","operationId":"create_report_share_token_api_customers__customer_id__share_tokens_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ShareTokenCreate"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Report Share Tokens","description":"List a customer's share tokens (metadata only — never the raw token).","operationId":"list_report_share_tokens_api_customers__customer_id__share_tokens_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/share-tokens/{token_id}":{"delete":{"summary":"Revoke Report Share Token","description":"Revoke a share token — the public link stops working immediately.","operationId":"revoke_report_share_token_api_customers__customer_id__share_tokens__token_id__delete","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"token_id","in":"path","required":true,"schema":{"type":"string","title":"Token Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/shared-report/{token}":{"get":{"summary":"Get Shared Report","description":"Public, read-only report view for a valid share token (no login).\n\nResolves the token → customer, then returns the same aggregated payload\nas GET /api/dashboard/{id} (served from its 15-min cache when warm),\nwrapped with share metadata. Expired/revoked tokens get 404.","operationId":"get_shared_report_api_shared_report__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/entity-setup":{"get":{"summary":"Get Entity Setup","description":"Persisted business context for the Entity Builder (empty data = unset).","operationId":"get_entity_setup_api_customers__customer_id__entity_setup_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Save Entity Setup","description":"Persist business context.\n\nAccepts three body shapes: the Lovable-wizardens nested EntitySetupState\n({company, sameAs, checklist, ...}), the flat Entity Builder field object,\nor either wrapped in {\"data\": {...}}. The stored object REPLACES the\nprevious one (frontend sends the full form state).\n\nNested state is stored verbatim under `state` (so the wizard can hydrate\neverything — sameAs, checklist, pressRelease — across devices) AND\nflattened to _ENTITY_SETUP_FIELDS at top level (so /api/entity-builder/plan\nfill-if-blank keeps working).\n\nP0.3 (2026-08-19) — to additive værn mod at erstatningen sletter andres\narbejde (onboarding-kortlægningen §2.5: en stale debounced autosave\nslettede pluginets same_as-bidrag):\n  1. Valgfrit body-felt `base_updated_at` (fra GET's updated_at): matcher\n     det ikke rækkens nuværende updated_at → 409 entity_setup_conflict\n     (klienten skal re-hente). Uden feltet: uændret adfærd (erstat).\n  2. Klient-tilføjede links (registreret i `plugin_same_as` af\n     /api/apps/entity-sources) re-merges ind i en erstattende skrivning,\n     der ikke selv bærer dem — medmindre klienten beviste aktualitet med\n     et matchende base_updated_at (så respekteres sletninger).","operationId":"save_entity_setup_api_customers__customer_id__entity_setup_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/same-as/verify":{"post":{"summary":"Verify Same As","description":"Verificér kundens gemte sameAs-links + find nye på deres website.\n\n- Hvert gemt link HEAD/GET'es og klassificeres live/blocked/dead/error.\n- Kundens forside scannes for JSON-LD `sameAs` — links der IKKE allerede\n  er gemt, returneres som forslag (\"fundet på dit website\").","operationId":"verify_same_as_api_customers__customer_id__same_as_verify_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/archetype":{"get":{"summary":"Get Customer Archetype","description":"Kundens arketype + onboarding-svar + auto-afledt forslag.\n\narchetype_source: \"user\" (bekræftet via onboarding) | \"derived\" (auto).\nNår archetype er null, viser frontend onboarding-skærmen og kan\nforudvælge `suggested.archetype`.","operationId":"get_customer_archetype_api_customers__customer_id__archetype_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Set Customer Archetype","description":"Sæt kundens arketype — enten direkte eller via de 3 onboarding-svar.\n\nOnboarding-svar (sell/area/buyer) vinder over direkte `archetype` og\ngemmes rå, så arketypen kan genberegnes. Source sættes til \"user\".\nSkifter arketypen reelt, genkøres analysen automatisk i samme ombæring\n(additivt `reanalysis`-felt i svaret — se _maybe_reanalyze_after_archetype).\n\n`area` ALENE er også et gyldigt svar (G-063). Virksomhedsprofilen stiller\nspørgsmålet \"hvor handler I?\" ved siden af by-feltet, uden at spørge om de\nto andre — og svaret er det, der afgør, om byen overhovedet må bruges i\nkundens spørgsmål. Uden denne gren kunne kortet kun sende alle tre, og så\nville en kunde, der aldrig havde svaret på \"hvad sælger I\", slet ikke\nkunne sige \"vi handler ikke lokalt\". Sell/buyer hentes fra det gemte, så\narketypen genberegnes på det fulde sæt og ikke på et halvt.","operationId":"set_customer_archetype_api_customers__customer_id__archetype_post","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchetypeUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/archetype/preview":{"get":{"summary":"Archetype Preview","description":"Onboarding-preview FØR kunden findes: afledt arketype + eksempel-prompts.\n\n`service` (2026-09-11) er kundens EGET ydelsesord, når kalderen har ét med\nreelt grundlag — typisk fra /api/onboarding/peek (sitets titel/H1). Uden\ndet bruges arketypens rolleord KUN når arketypen selv hviler på noget\n(NACE, signaler eller de tre svar). En default-arketype har intet at\nsætte i ydelses-slotten, og så udelades de skabeloner der kræver ét —\nfærre prompts er ærligt, \"leverandør af rådgiver\" er det ikke (G-003;\nseotekster.dk-dogfood 2026-09-10).\n\nErstatter Lovable-onboardingens legacy branche-dropdown: previewet viser\nnu prompts fra ARCHETYPE_PROMPTS (det vi faktisk måler med) for den\narketype, KT1-kæden vil aflede ved oprettelse — på markedets sprog.\n\nRen beregning: ingen DB-skrivning, ingen eksterne kald. Registerdata\n(nace_code/nace_description) sendes med af kalderen fra det CVR-opslag,\nfrontenden allerede har lavet (/api/nordic/company) — endpointet slår\nALDRIG selv op (ville fordoble Nordic Data-kosten pr. onboarding).\n\nGives sell (+ evt. area/buyer) bruges de tre onboarding-svar i stedet\n(source=\"user\") — samme mapping som POST /api/customers/{id}/archetype,\nså previewet aldrig kan afvige fra det, der senere gemmes. Afledningen\ner et FORSLAG (source=\"derived\"), aldrig en beslutning — kunden justerer\nvia de tre spørgsmål, og brand er kundens eget navn, ikke registrets.","operationId":"archetype_preview_api_archetype_preview_get","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string","default":"","title":"Domain"}},{"name":"market","in":"query","required":false,"schema":{"type":"string","default":"","title":"Market"}},{"name":"company_name","in":"query","required":false,"schema":{"type":"string","default":"","title":"Company Name"}},{"name":"nace_code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Nace Code"}},{"name":"nace_description","in":"query","required":false,"schema":{"type":"string","default":"","title":"Nace Description"}},{"name":"city","in":"query","required":false,"schema":{"type":"string","default":"","title":"City"}},{"name":"sell","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sell"}},{"name":"area","in":"query","required":false,"schema":{"type":"string","default":"","title":"Area"}},{"name":"buyer","in":"query","required":false,"schema":{"type":"string","default":"","title":"Buyer"}},{"name":"service","in":"query","required":false,"schema":{"type":"string","default":"","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/archetype-backfill":{"post":{"summary":"Archetype Backfill","description":"Backfill: auto-afled arketype for kunder uden en (HERMES-beskyttet).\n\noverwrite=true genberegner også kunder med source='derived' (rører ALDRIG\nkunder med source='user').","operationId":"archetype_backfill_admin_archetype_backfill_post","parameters":[{"name":"overwrite","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Overwrite"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/waitlist":{"post":{"summary":"Join Waitlist","description":"Join the waitlist — stores e-mail + source in Supabase.\n\nFail-open by design: any storage error still returns ok:true so the\nmarketing flow never breaks; duplicates are ignored silently.","operationId":"join_waitlist_api_waitlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistSignup"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/benchmark/run":{"post":{"summary":"Api Benchmark Run","description":"Start (eller genoptag) benchmark-gennemkørslen i baggrunden.\n\nIdempotent: domæner med en score nyere end max_age_days springes over,\nmedmindre force=true. Én kørsel ad gangen pr. proces.","operationId":"api_benchmark_run_api_benchmark_run_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"max_age_days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Max Age Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/benchmark/status":{"get":{"summary":"Api Benchmark Status","description":"Fremdrift for den igangværende/seneste benchmark-kørsel.\n\nBruger den persisterede tilstand (migration-051) så status overlever en\nRailway-redeploy i stedet for at nulstille til \"aldrig kørt\".","operationId":"api_benchmark_status_api_benchmark_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/content/entity-salience":{"get":{"summary":"Content Entity Salience","description":"Analyze how clearly a company stands out as an entity in page content.\n\nProvide either `url` (page is fetched) or `text` directly, plus `company`.\nUses Google Cloud Natural Language API (fail-open).","operationId":"content_entity_salience_content_entity_salience_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}},{"name":"company","in":"query","required":false,"schema":{"type":"string","default":"","title":"Company"}},{"name":"text","in":"query","required":false,"schema":{"type":"string","default":"","title":"Text"}},{"name":"language","in":"query","required":false,"schema":{"type":"string","default":"da","title":"Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/properties":{"get":{"summary":"Ga4 Properties","description":"List the connected Google account's GA4 properties (for a picker).\n\nAdditivt 2026-08-30: hver ejendom baerer nu `domain_match`\n(\"match\"/\"mismatch\"/\"unknown\"), `measures` (domaenerne dens datastreams\nfaktisk maaler) og `name_hint`; svaret baerer `customer_domain` og\n`has_match`. En flade der bygger en vaelger SKAL bruge dem — ellers\ntilbyder den kunden et andet websites tal.","operationId":"ga4_properties_ga4_properties_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/select-property":{"post":{"summary":"Ga4 Select Property","description":"Persist which GA4 property the customer's dashboard should read.\n\nEn ejendom hvis datastreams bevisligt maaler et ANDET domaene afvises\n(svaret baerer domain_match=\"mismatch\"). Tom property_id rydder valget —\n\"ingen af disse\" skal kunne siges her ogsaa, ikke kun paa vaelgersiden.","operationId":"ga4_select_property_ga4_select_property_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"property_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Property Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/status":{"get":{"summary":"Ga4 Status","description":"One-call GA4 status for the dashboard settings card: connection,\nselected property, available properties, needs_selection/needs_reconnect\nand last sync state.","operationId":"ga4_status_ga4_status_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/ga4-property-audit":{"get":{"summary":"Admin Ga4 Property Audit","description":"Måler hver kundes gemte GA4-ejendom faktisk KUNDENS website?\n\nFindes fordi vælgeren indtil 2026-08-30 tvang et valg, ogsaa naar kontoen\nikke havde én eneste ejendom for kundens domaene. De kunder har fremmede\ntal staaende i rapporter, digests og alarmer — de skal kunne findes.\n\n`clear=true` nulstiller de BEVISTE mismatch (uafgjorte roeres aldrig).\nSamme logik som scripts/ga4_property_domain_audit.py; endpointet findes,\nfordi prod-noeglerne bor i prod og ikke paa en laptop.","operationId":"admin_ga4_property_audit_admin_ga4_property_audit_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"clear","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Clear"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/picker-link":{"get":{"summary":"Ga4 Picker Link","description":"Short-lived, signed URL to the backend property-picker page — for\nalready-connected customers who need to (re)choose their GA4 property.","operationId":"ga4_picker_link_ga4_picker_link_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ga4/choose-property":{"get":{"summary":"Ga4 Choose Property Page","description":"Customer-facing GA4 property picker. Auth = signed short-lived state\n(same model as the OAuth callback) — no session or API key needed.\n\nReglen siden dogfood 2026-08-30: siden maa aldrig tvinge et valg. Har\nkontoen ingen ejendom for kundens eget domaene, er ETHVERT valg forkert,\nog det aerlige svar er \"vi fandt ingen\" — ikke en forvalgt radioknap.","operationId":"ga4_choose_property_page_ga4_choose_property_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Ga4 Choose Property Submit","description":"Persist the picker choice (signed-state protected), then send the\ncustomer on to their dashboard.","operationId":"ga4_choose_property_submit_ga4_choose_property_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/ga4/timeseries":{"get":{"summary":"Ga4 Timeseries","description":"Daily GA4 sessions/users/pageviews for the customer's site.","operationId":"ga4_timeseries_ga4_timeseries_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/content-freshness":{"get":{"summary":"Content Freshness","description":"Indholdsalder pr. side fra sitemap-lastmod (KB §7: forældet indhold\nmister AI-citater ~3× hurtigere). Ærligt: uden sitemap/lastmod svarer vi\n'unknown' — vi estimerer aldrig en alder vi ikke kan se.","operationId":"content_freshness_api_customers__customer_id__content_freshness_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/recap-email-sweep":{"post":{"summary":"Admin Recap Sweep","description":"Manuel trigger af den månedlige værdi-recap (test/genkørsel).\ndry_run bygger + renderer uden at sende; force ignorerer idempotens-loggen.","operationId":"admin_recap_sweep_admin_recap_email_sweep_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/recap-preview":{"get":{"summary":"Recap Preview","description":"Preview af kundens værdi-recap (data + renderet mail) — sender intet.","operationId":"recap_preview_api_customers__customer_id__recap_preview_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/email/unsubscribe":{"get":{"summary":"Email Unsubscribe","description":"Offentligt afmeld-link fra recap-mailen (HMAC-signeret token).\n\nBevidst UDEN auth-dependency: modtageren klikker fra sin mailklient.\nKan KUN sætte customers.email_opt_out=true for det signerede customer_id\n— ingen data læses eller returneres. Idempotent.","operationId":"email_unsubscribe_email_unsubscribe_get","parameters":[{"name":"cid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cid"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ga4/ai-referrers":{"get":{"summary":"Ga4 Ai Referrers","description":"Sessions/brugere FRA AI-assistenter (ChatGPT, Perplexity, Copilot, …)\nvia GA4 sessionSource — det direkte \"AI sender dig besøgende\"-bevis.\n\nStien matcher hvad dashboardet oprindeligt forventede (AiReferrersCard\nblev fjernet fordi endpointet manglede). Svar: {period, totals, by_source,\ntimeseries, property_id} eller {error}. NB: AI-referrer-attribution\nUNDERTÆLLER (mange AI-flader stripper referreren) — tallet er et gulv,\nikke et totalbillede; UI'et skal formidle det ærligt.","operationId":"ga4_ai_referrers_api_ga4_ai_referrers_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plausible/connect":{"post":{"summary":"Plausible Connect","description":"Forbind kundens EGEN Plausible-konto som kilde til AI-trafik.\n\nVi hoster ikke analytics for kunden — vi læser kundens konto med kundens\negen Stats API-nøgle. NB: Plausibles Stats API kræver Business-planen\nhos dem; en kunde på Starter/Growth får en tydelig besked, ikke en tom\ngraf. Nøglen returneres ALDRIG igen.","operationId":"plausible_connect_api_plausible_connect_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlausibleConnectRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plausible/status":{"get":{"summary":"Plausible Status","description":"Er der en Plausible-forbindelse, og på hvilket domæne.","operationId":"plausible_status_api_plausible_status_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plausible/disconnect":{"post":{"summary":"Plausible Disconnect","description":"Fjern forbindelsen. Nøglen er kundens egen og styres hos Plausible.","operationId":"plausible_disconnect_api_plausible_disconnect_post","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-traffic/referrers":{"get":{"summary":"Ai Traffic Referrers","description":"AI-henvisningstrafik fra den kilde kunden HAR — GA4 eller Plausible.\n\nDen kilde-agnostiske afløser for /api/ga4/ai-referrers (som bliver\nstående uændret: to frontends deler backenden, og svarformater brydes\naldrig). Svaret bærer `source` = \"ga4\" | \"plausible\" | null, så fladen\nkan sige hvor tallet kommer fra — og sige det HØJT, hvis en forbundet\nkilde fejler, i stedet for at vise en tom graf.","operationId":"ai_traffic_referrers_api_ai_traffic_referrers_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":28,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-traffic/sources":{"get":{"summary":"Ai Traffic Sources","description":"Hvilke analytics-kilder er forbundet — til indstillingskortet.","operationId":"ai_traffic_sources_api_ai_traffic_sources_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-traffic/source":{"post":{"summary":"Ai Traffic Set Source","description":"Kunden vælger sin besøgsmåling: Google Analytics, Plausible — eller\ningen.\n\n\"Ingen\" er et fuldgyldigt valg, ikke en mangel: mange kunder vil kun\ntilkoble Search Console. Valget styrer AI-trafik-kortet, digest-linjen\nog månedsrapporten, så de tre flader aldrig kan blive uenige.","operationId":"ai_traffic_set_source_api_ai_traffic_source_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiTrafficSourceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gsc/impact":{"get":{"summary":"Gsc Impact","description":"Before/after impact view: GSC daily clicks/impressions timeline with\nverified GEO fixes overlaid, plus a per-fix before/after summary.\n\nResponse:\n    {\n      \"timeseries\": [{date, clicks, impressions, ctr, position}, ...],\n      \"fix_events\": [{dimension, dimension_name, verified, created_at,\n                      previous_score, new_score,\n                      impact: {before: {...}, after: {...}, days_compared}}],\n      \"score_history\": [{score, grade, timestamp}, ...]\n    }","operationId":"gsc_impact_gsc_impact_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tier/upgrade":{"get":{"summary":"Upgrade Tier","description":"Upgrade a customer's tier — kun betalingsflowet (HERMES).\n\nCalled after payment confirmation.","operationId":"upgrade_tier_api_tier_upgrade_get","parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Customer Id"}},{"name":"tier","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tier"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dataset/stats":{"get":{"summary":"Dataset Stats Endpoint","description":"Aggregate AI Visibility Database benchmarks (HERMES-protected).\n\nGuarded by the shared HERMES key (X-HERMES-API-Key) — this is proprietary\ncross-domain \"AI Search Observatory\" data, not a public endpoint. Returns\ntotals, average GEO score, schema adoption rates, and breakdowns by\nindustry / country / company size over scan_events.","operationId":"dataset_stats_endpoint_api_dataset_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/dataset/backfill":{"post":{"summary":"Dataset Backfill Endpoint","description":"Seed / retro-enrich the AI Visibility Database (HERMES-protected, admin).\n\nRuns in the background and returns immediately. Body:\n  - {\"mode\":\"enrich\",\"limit\":300,\"only_missing_revenue\":false}\n      Nordic-only retro-enrichment of existing rows (no re-scan). Set\n      only_missing_revenue=true to fill revenue_band on rows enriched before\n      revenue support existed.\n  - {\"mode\":\"seed\",\"domains\":[\"acme.dk\", ...],\"source\":\"seed\"}\n      Runs full GEO scans on the given domains to grow the dataset (capped).","operationId":"dataset_backfill_endpoint_api_dataset_backfill_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/geo-score/history":{"get":{"summary":"Geo Score History","description":"Get historical GEO Scores for a domain (score-over-time).\n\nQuery params:\n    url:  Domain to get history for (e.g. 'example.com')","operationId":"geo_score_history_geo_score_history_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score/widget.js":{"get":{"summary":"Geo Score Widget Js","description":"Embeddable GEO Score widget — paste this script tag on any website.\n\nUsage:\n    <script src=\"https://geoa.app/geo-score/widget.js\" defer></script>\n\nShows a floating badge with the site's current GEO Score.\nClick opens the full report on geoa.app.","operationId":"geo_score_widget_js_geo_score_widget_js_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/geo-score/embed-snippet":{"get":{"summary":"Geo Score Embed Snippet","description":"Returns the copy-paste HTML snippet for embedding the GEO Score widget.","operationId":"geo_score_embed_snippet_geo_score_embed_snippet_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/geo-score/pdf":{"get":{"summary":"Geo Score Pdf","description":"Generate a PDF GEO Score report for any domain.\n\nQuery params:\n    url:  Domain to check and generate report for (e.g. 'example.com')","operationId":"geo_score_pdf_geo_score_pdf_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score/slack-webhook":{"post":{"summary":"Geo Score Set Slack Webhook","description":"Configure Slack webhook for GEO Score alerts.\n\nPOST with JSON body: {\"url\": \"https://hooks.slack.com/services/...\"}","operationId":"geo_score_set_slack_webhook_geo_score_slack_webhook_post","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/geo-score/check":{"get":{"summary":"Geo Score Check Page","description":"One-time GEO Score check — instant, no login, returns a clean HTML page.\n\nQuery params:\n    url:  Domain to check (e.g. 'example.com')","operationId":"geo_score_check_page_geo_score_check_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"type":"string","default":"","title":"Url"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/customers/{customer_id}/citations/sources":{"get":{"summary":"Get Citation Sources Endpoint","description":"Get top 10-20 third-party domains that AI models cited in the latest run.\n\nThis is the Kilde-attribution feature — it transforms GEO from a score into\na PR strategy by showing customers exactly which external sources AI uses\n(Trustpilot, Reddit, Krak, Bolius, brancheblogs) and what to do about it.\n\nReturns 200 with sources[] (empty if pipeline hasn't run yet).\nUses 503 for \"pipeline never ran\" vs 200 empty for \"no sources found\".","operationId":"get_citation_sources_endpoint_api_customers__customer_id__citations_sources_get","parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","title":"Customer Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/companies":{"get":{"summary":"Growth List Companies","description":"List all growth-engine companies with filtering and pagination.\n\nUsed by Lovable's LeadsTable for filter/sort/paginate.\n\nAuth: HÅRD workspace-håndhævelse (A0). Var før et åbent endpoint der\nreturnerede ALLE workspaces' leads — Lovable sendte allerede workspace_id\npå hvert kald, så håndhævelsen er ikke-brydende.","operationId":"growth_list_companies_api_growth_companies_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"industry","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"}},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"}},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"dk","title":"Country"}},{"name":"min_score","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"default":0,"title":"Min Score"}},{"name":"max_score","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"default":100,"title":"Max Score"}},{"name":"stage","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage"}},{"name":"contact_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Status"}},{"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"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","default":"added_at","title":"Sort By"}},{"name":"sort_dir","in":"query","required":false,"schema":{"type":"string","default":"desc","title":"Sort Dir"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/companies/{company_id}":{"get":{"summary":"Growth Get Company","description":"Get a single company with full detail.\n\nUsed by Lovable's LeadDetail drawer.\nReturns 404 if company not found.","operationId":"growth_get_company_api_growth_companies__company_id__get","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Growth Update Company","description":"Update a company's mutable fields.\n\nLovable kalder PATCH når bruger:\n- Flytter lead i pipeline (stage)\n- Opdaterer kontaktstatus\n- Tilføjer noter / tags","operationId":"growth_update_company_api_growth_companies__company_id__patch","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/stats/overview":{"get":{"summary":"Growth Stats Overview","description":"Dashboard overview: KPI counts, averages, distributions.\n\nUsed by Lovable's GrowthDashboard for KPI cards and charts.","operationId":"growth_stats_overview_api_growth_stats_overview_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/stats/pipeline":{"get":{"summary":"Growth Pipeline Stats","description":"Pipeline-specific stats: stage counts + conversion rates.\n\nUsed by Lovable's PipelineBoard for header metrics.","operationId":"growth_pipeline_stats_api_growth_stats_pipeline_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/stats/activity":{"get":{"summary":"Growth Activity","description":"Recent activity feed: new companies, audits, stage changes.\n\nUsed by Lovable's GrowthDashboard activity feed.","operationId":"growth_activity_api_growth_stats_activity_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/health":{"get":{"summary":"Growth Health","description":"Health check for the growth engine.\n\nA0: tallene er workspace-scopede. Uden et gyldigt workspace svares der\nstadig ok (det ER en health check), men med nul-tal — aggregerede tal på\ntværs af kunder er ikke sundhedsinfo, det er en lækage.","operationId":"growth_health_api_growth_health_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/pipeline":{"get":{"summary":"Growth Pipeline","description":"Pipeline grouped by stage for Kanban board.\n\nLovable's PipelineBoard forventer:\n{ \"stages\": { \"lead\": [...], \"contacted\": [...], ... } }","operationId":"growth_pipeline_api_growth_pipeline_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/emails":{"get":{"summary":"Growth Email Drafts","description":"List email drafts with optional filters.\n\nLovable's EmailStudio forventer liste af drafts.","operationId":"growth_email_drafts_api_growth_outreach_emails_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"industry","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/emails/{draft_id}":{"get":{"summary":"Growth Get Email Draft","description":"Get a single email draft by ID.\n\nLovable's send edge function bruger denne til at re-fetche\ndraften server-side lige før send (undgår race conditions).\n\nAuth: HÅRD workspace-håndhævelse (require_workspace) — dette er et\nobject-level endpoint der udleverer selve draft-indholdet, så det løftes\nud af den monitor-first legacy-growth-zone (som kun logger indtil\nGROWTH_WS_ENFORCE). Begge kaldere sender altid ?workspace_id + JWT\n(send-edge-fn + EmailStudio), så håndhævelsen er ikke-brydende.","operationId":"growth_get_email_draft_api_growth_outreach_emails__draft_id__get","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Growth Update Email Draft","description":"Update email draft status (approve, skip, send, etc.).\n\nLovable kalder PATCH efter godkendelse i EmailStudio.\nVed send-status modtager vi provider_message_id + sent_at.\n\nAuth: HÅRD workspace-håndhævelse (require_workspace) — state-mutating\n(kan markere en draft 'send'), så det må ikke kunne kaldes uautentificeret\nmens legacy-growth stadig er monitor-first. Begge kaldere sender altid\n?workspace_id + JWT (send-edge-fn + EmailStudio-hook), så ikke-brydende.","operationId":"growth_update_email_draft_api_growth_outreach_emails__draft_id__patch","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/linkedin":{"get":{"summary":"Growth Linkedin Posts","description":"List LinkedIn posts with optional filters.\n\nLovable's LinkedInStudio forventer liste af posts.","operationId":"growth_linkedin_posts_api_growth_outreach_linkedin_get","parameters":[{"name":"industry","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/audit":{"post":{"summary":"Growth Run Audit","description":"Run GEO audit on a domain.\n\nLovable's Audits-side kalder denne via useRunAudit.\nKører run_geo_score on-demand via Geoa API.","operationId":"growth_run_audit_api_growth_audit_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/schedule/config":{"get":{"summary":"Growth Get Scan Config","description":"Get scan configuration.\n\nLovable's Scans-side viser hvilke brancher/byer/frekvens der scannes.","operationId":"growth_get_scan_config_api_growth_schedule_config_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Growth Update Scan Config","description":"Update scan configuration.\n\nLovable's Scans-side gemmer brugernes valg af brancher, byer, frekvens.","operationId":"growth_update_scan_config_api_growth_schedule_config_patch","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/schedule/scan":{"post":{"summary":"Growth Trigger Scan","description":"Trigger a manual scan.\n\nLovable's Scans-side \"Scan nu\"-knap. Kører scannet med det samme\n(cluster-lås i run_scan forhindrer dobbeltkørsel — kører et scan\nallerede, returneres status \"skipped\" i stedet for dobbelt regning).","operationId":"growth_trigger_scan_api_growth_schedule_scan_post","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/schedule/history":{"get":{"summary":"Growth Scan History","description":"Get scan history.\n\nLovable's Scans-side viser tidligere scans.","operationId":"growth_scan_history_api_growth_schedule_history_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/compliance/screen":{"post":{"summary":"Growth Compliance Screen","description":"Compliance screening of a company.\n\nLovable's Compliance-side forventer:\n{ query, cvr?, status, sanctions[], pep[], adverse_media[] }\nBruger Nordic Data API når tilgængelig.","operationId":"growth_compliance_screen_api_growth_compliance_screen_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/competitors":{"get":{"summary":"Growth Competitors","description":"Get competitors for a domain or industry+city.\n\nTwo modes:\n- ?domain=aarhusvvs.dk — find konkurrenter til specifikt domæne\n- ?industry=VVS&city=Aarhus — find top virksomheder i branchen (leaderboard)\n\nLovable's UI er branche-først (industry+city).","operationId":"growth_competitors_api_growth_competitors_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"}},{"name":"industry","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"}},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/signals":{"get":{"summary":"Growth Signals","description":"Get intent signals for companies.\n\nLovable's Signals-side viser GEO-analyser, opportunities og news.","operationId":"growth_signals_api_growth_signals_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}},{"name":"company_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Id"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},{"name":"min_score","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"default":0,"title":"Min Score"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/campaigns":{"get":{"summary":"Growth Campaigns","description":"Kampagneoversigt — afledt af email-drafts + LinkedIn-posts pr. branche.\n\nRadar's Outreach-side forventer {campaigns: [...]}. Der findes ikke et\nseparat kampagne-objekt i backenden; tallene er faktiske draft-statusser.","operationId":"growth_campaigns_api_growth_outreach_campaigns_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/emails/{draft_id}/regenerate":{"post":{"summary":"Growth Regenerate Email","description":"Regenerér en email-draft via LLM. 503 hvis AI-integrationen mangler.","operationId":"growth_regenerate_email_api_growth_outreach_emails__draft_id__regenerate_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/linkedin/{post_id}":{"patch":{"summary":"Growth Update Linkedin","description":"Opdatér LinkedIn-opslags status (approve, post, skip).","operationId":"growth_update_linkedin_api_growth_outreach_linkedin__post_id__patch","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/outreach/linkedin/{post_id}/regenerate":{"post":{"summary":"Growth Regenerate Linkedin","description":"Regenerér et LinkedIn-opslag via LLM. 503 hvis AI-integrationen mangler.","operationId":"growth_regenerate_linkedin_api_growth_outreach_linkedin__post_id__regenerate_post","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"string","title":"Post Id"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/companies/{company_id}/competitors":{"get":{"summary":"Growth Company Competitors","description":"Konkurrenter for et specifikt CRM-lead (via domænet).","operationId":"growth_company_competitors_api_growth_companies__company_id__competitors_get","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/companies/{company_id}/brief":{"get":{"summary":"Growth Get Brief","description":"Hent gemt salgs-brief for et lead. 404 hvis intet er genereret endnu.","operationId":"growth_get_brief_api_growth_companies__company_id__brief_get","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Growth Generate Brief","description":"Generér (og gem) et salgs-brief for et lead.\n\nGrundlaget er altid faktiske CRM/GEO-data; hvis AI-integrationen er sat,\nberiges briefet med en genereret samtaleåbner.","operationId":"growth_generate_brief_api_growth_companies__company_id__brief_post","parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"string","title":"Company Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/growth/copilot/chat":{"post":{"summary":"Growth Copilot Chat","description":"Copilot: samtale med værktøjer mod dette workspaces egne data.","operationId":"growth_copilot_chat_api_growth_copilot_chat_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/growth/agent/suggest":{"post":{"summary":"Growth Agent Suggest","description":"Agent: dagens fem firmaer at kontakte, valgt ud fra leads og signaler.","operationId":"growth_agent_suggest_api_growth_agent_suggest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/growth/knowledge/docs":{"get":{"summary":"Growth Knowledge List","description":"Bureauets egne dokumenter i dette workspace.","operationId":"growth_knowledge_list_api_growth_knowledge_docs_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Growth Knowledge Add","description":"Gem et dokument. Teksten deles i stykker — ingen embeddings, se\nsupabase/migrations/062a-growth-knowledge.sql.","operationId":"growth_knowledge_add_api_growth_knowledge_docs_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/growth/knowledge/docs/{doc_id}":{"delete":{"summary":"Growth Knowledge Delete","description":"Slet et dokument og dets stykker.","operationId":"growth_knowledge_delete_api_growth_knowledge_docs__doc_id__delete","parameters":[{"name":"doc_id","in":"path","required":true,"schema":{"type":"string","title":"Doc Id"}},{"name":"workspace_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AIScanRequest":{"properties":{"industry_slug":{"type":"string","title":"Industry Slug"},"country":{"type":"string","title":"Country","default":"dk"},"platforms":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Platforms"},"max_cities":{"type":"integer","title":"Max Cities","default":20},"run_id":{"type":"string","title":"Run Id","default":""}},"type":"object","required":["industry_slug"],"title":"AIScanRequest"},"AccountDelete":{"properties":{"confirm":{"type":"string","title":"Confirm","default":""}},"type":"object","title":"AccountDelete"},"ActionStatusUpdate":{"properties":{"status":{"type":"string","title":"Status"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","required":["status"],"title":"ActionStatusUpdate","description":"Request body for PATCH /api/customers/{customer_id}/actions/{action_id}"},"ActivationEventIn":{"properties":{"event":{"type":"string","title":"Event"},"meta":{"additionalProperties":true,"type":"object","title":"Meta","default":{}}},"type":"object","required":["event"],"title":"ActivationEventIn"},"AiTrafficSourceRequest":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"source":{"type":"string","title":"Source"}},"type":"object","required":["customer_id","source"],"title":"AiTrafficSourceRequest","description":"Kundens valg af besøgsmåling. Egen model, saa feltnavne ikke\nkolliderer med hver anden session i filen."},"AlertSettingsPatch":{"properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"triggers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Triggers"},"channels":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Channels"}},"type":"object","title":"AlertSettingsPatch"},"AppReviewKitRequest":{"properties":{"purchase":{"type":"string","maxLength":200,"title":"Purchase","default":""},"date":{"type":"string","maxLength":40,"title":"Date","default":""},"staff":{"type":"string","maxLength":40,"title":"Staff","default":""},"lang":{"type":"string","maxLength":5,"title":"Lang","default":""}},"type":"object","title":"AppReviewKitRequest"},"ArchetypeUpdateRequest":{"properties":{"archetype":{"type":"string","title":"Archetype","default":""},"sell":{"type":"string","title":"Sell","default":""},"area":{"type":"string","title":"Area","default":""},"buyer":{"type":"string","title":"Buyer","default":""}},"type":"object","title":"ArchetypeUpdateRequest"},"ArticleBody":{"properties":{"material":{"type":"string","title":"Material","default":""},"source_url":{"type":"string","title":"Source Url","default":""},"image_urls":{"items":{"type":"string"},"type":"array","title":"Image Urls","default":[]},"image_alts":{"items":{"type":"string"},"type":"array","title":"Image Alts","default":[]},"answers":{"additionalProperties":true,"type":"object","title":"Answers","default":{}},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"ArticleBody"},"ArticleDerive":{"properties":{"formats":{"items":{},"type":"array","title":"Formats"}},"type":"object","required":["formats"],"title":"ArticleDerive"},"ArticleGenerate":{"properties":{"brief_id":{"type":"string","title":"Brief Id"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"depth":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Depth"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"page_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Type"}},"type":"object","required":["brief_id"],"title":"ArticleGenerate"},"ArticleLocalize":{"properties":{"language":{"type":"string","title":"Language"}},"type":"object","required":["language"],"title":"ArticleLocalize"},"ArticleRefine":{"properties":{"mode":{"type":"string","title":"Mode"},"instruction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instruction"}},"type":"object","required":["mode"],"title":"ArticleRefine"},"BatchScanRequest":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"prompt_count":{"type":"integer","title":"Prompt Count","default":5},"model":{"type":"string","title":"Model","default":""}},"type":"object","required":["customer_id"],"title":"BatchScanRequest"},"BillingSync":{"properties":{"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type"},"auth_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth User Id"},"auth_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Issuer"},"stripe_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Customer Id"},"stripe_subscription_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Subscription Id"},"plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan"},"interval":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interval"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"current_period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Period End"},"livemode":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Livemode"},"cancel_at_period_end":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cancel At Period End"}},"type":"object","title":"BillingSync","description":"Præcis den krop, platformens `toBillingSync` sender. Ukendte felter\nignoreres (Stripe og platformen må gerne tilføje noget uden at vælte os)."},"BingSiteSelection":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"site_url":{"type":"string","title":"Site Url"}},"type":"object","required":["customer_id","site_url"],"title":"BingSiteSelection"},"BlogPublish":{"properties":{"youtube_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Youtube Id"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"noindex":{"type":"boolean","title":"Noindex","default":true},"enforce_quality":{"type":"boolean","title":"Enforce Quality","default":true}},"type":"object","title":"BlogPublish"},"BrandMemoryUpdate":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["data"],"title":"BrandMemoryUpdate"},"BrandTermsUpdate":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"add":{"items":{},"type":"array","title":"Add","default":[]},"remove":{"items":{},"type":"array","title":"Remove","default":[]}},"type":"object","required":["customer_id"],"title":"BrandTermsUpdate"},"BriefCreate":{"properties":{"topic":{"type":"string","maxLength":500,"minLength":1,"title":"Topic"},"language":{"type":"string","title":"Language","default":"da"},"brief":{"additionalProperties":true,"type":"object","title":"Brief"},"source":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Source"}},"type":"object","required":["topic","brief"],"title":"BriefCreate"},"BriefQuotaCheck":{"properties":{"topic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Topic"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"BriefQuotaCheck","description":"Hvad genereringen handler om — kun til log-linjen og teaseren."},"BriefUpdate":{"properties":{"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"brief":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Brief"}},"type":"object","title":"BriefUpdate"},"BusinessValueRequest":{"properties":{"dimensions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Dimensions","default":[]}},"type":"object","title":"BusinessValueRequest"},"CalendarBody":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","title":"CalendarBody"},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","title":"Current Password"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest"},"ChatBody":{"properties":{"product":{"type":"string","title":"Product"},"messages":{"items":{"$ref":"#/components/schemas/Message"},"type":"array","title":"Messages"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"page":{"anyOf":[{"$ref":"#/components/schemas/PageContext"},{"type":"null"}]},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id"}},"type":"object","required":["product"],"title":"ChatBody"},"CitySeriesGenerate":{"properties":{"brief_id":{"type":"string","title":"Brief Id"},"cities":{"items":{},"type":"array","title":"Cities"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},"type":"object","required":["brief_id","cities"],"title":"CitySeriesGenerate"},"ClaimSiteRequest":{"properties":{"token":{"type":"string","title":"Token","default":""}},"type":"object","title":"ClaimSiteRequest"},"CompetitorAdd":{"properties":{"domain":{"type":"string","title":"Domain"},"name":{"type":"string","title":"Name","default":""}},"type":"object","required":["domain"],"title":"CompetitorAdd"},"CompetitorCreate":{"properties":{"domain":{"type":"string","title":"Domain"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["domain"],"title":"CompetitorCreate"},"ConsentBatchIn":{"properties":{"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id"},"consent":{"$ref":"#/components/schemas/ConsentIn"},"contacts":{"items":{"$ref":"#/components/schemas/ConsentContact"},"type":"array","title":"Contacts"}},"type":"object","required":["consent","contacts"],"title":"ConsentBatchIn","description":"Én registrering eller en import. Samme `consent` gælder alle kontakter —\nimporten spørger én gang: \"Har alle i filen sagt ja?\"."},"ConsentContact":{"properties":{"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"name":{"type":"string","title":"Name","default":""}},"type":"object","title":"ConsentContact"},"ConsentIn":{"properties":{"given":{"type":"boolean","title":"Given","default":true},"source":{"type":"string","title":"Source","default":""},"given_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Given At"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"type":"object","title":"ConsentIn","description":"Kundens ja (eller nej) til at få et spørgsmål om en anmeldelse.\n\n`source` er HVORDAN kunden sagde det — det er selve beviset. `note` er en\nfri linje til personalet (\"sagt ved kassen 14:20, registreret af Emma\")."},"ConsentLookupIn":{"properties":{"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}},"type":"object","title":"ConsentLookupIn"},"ContentCreate":{"properties":{"format":{"type":"string","title":"Format","default":"article"},"language":{"type":"string","title":"Language","default":"da"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"},"brief_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brief Id"},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"},"status":{"type":"string","title":"Status","default":"draft"}},"type":"object","title":"ContentCreate"},"ContentGateCheck":{"properties":{"text":{"type":"string","title":"Text"},"customer_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Domain"}},"type":"object","required":["text"],"title":"ContentGateCheck"},"ContentUpdate":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body"},"meta":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Meta"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"scheduled_for":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled For"},"published_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published Url"}},"type":"object","title":"ContentUpdate"},"CustomerCreate":{"properties":{"email":{"type":"string","title":"Email"},"domain":{"type":"string","title":"Domain"},"company_name":{"type":"string","title":"Company Name","default":""},"lightweight":{"type":"boolean","title":"Lightweight","default":false},"user_id":{"type":"string","title":"User Id","default":""},"onboarding_sell":{"type":"string","title":"Onboarding Sell","default":""},"onboarding_area":{"type":"string","title":"Onboarding Area","default":""},"onboarding_buyer":{"type":"string","title":"Onboarding Buyer","default":""},"registry_id":{"type":"string","title":"Registry Id","default":""},"registry_country":{"type":"string","title":"Registry Country","default":""},"brand_confirmed":{"type":"boolean","title":"Brand Confirmed","default":false},"service_term":{"type":"string","title":"Service Term","default":""}},"type":"object","required":["email","domain"],"title":"CustomerCreate"},"DataReportGenerate":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"DataReportGenerate"},"DraftIn":{"properties":{"review_ids":{"items":{"type":"string"},"type":"array","maxItems":5,"title":"Review Ids"}},"type":"object","title":"DraftIn"},"DraftRequest":{"properties":{"reviews":{"items":{"$ref":"#/components/schemas/ReviewIn"},"type":"array","maxItems":5,"title":"Reviews"}},"type":"object","title":"DraftRequest"},"EmbedChatBody":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/EmbedMessage"},"type":"array","title":"Messages"},"lang":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}},"type":"object","title":"EmbedChatBody"},"EmbedLeadBody":{"properties":{"name":{"type":"string","title":"Name"},"email":{"type":"string","title":"Email"},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"need":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Need"},"consent":{"type":"boolean","title":"Consent","default":false},"lang":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"},"messages":{"items":{"$ref":"#/components/schemas/EmbedMessage"},"type":"array","title":"Messages"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}},"type":"object","required":["name","email"],"title":"EmbedLeadBody"},"EmbedMessage":{"properties":{"role":{"type":"string","title":"Role"},"content":{"type":"string","title":"Content"}},"type":"object","required":["role","content"],"title":"EmbedMessage"},"EntityBuilderPlanRequest":{"properties":{"domain":{"type":"string","title":"Domain","default":""},"brand":{"type":"string","title":"Brand","default":""},"company_name":{"type":"string","title":"Company Name","default":""},"alt_name":{"type":"string","title":"Alt Name","default":""},"legal_name":{"type":"string","title":"Legal Name","default":""},"cvr":{"type":"string","title":"Cvr","default":""},"vat":{"type":"string","title":"Vat","default":""},"website":{"type":"string","title":"Website","default":""},"city":{"type":"string","title":"City","default":""},"country_name":{"type":"string","title":"Country Name","default":""},"founded":{"type":"string","title":"Founded","default":""},"founder":{"type":"string","title":"Founder","default":""},"founder_linkedin":{"type":"string","title":"Founder Linkedin","default":""},"email":{"type":"string","title":"Email","default":""},"industry":{"type":"string","title":"Industry","default":""},"business_type":{"type":"string","title":"Business Type","default":""},"archetype":{"type":"string","title":"Archetype","default":""},"country_code":{"type":"string","title":"Country Code","default":""},"language":{"type":"string","title":"Language","default":""},"tagline_en":{"type":"string","title":"Tagline En","default":""},"description":{"type":"string","title":"Description","default":""},"description_en":{"type":"string","title":"Description En","default":""},"target_audience":{"type":"string","title":"Target Audience","default":""},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"same_as":{"items":{"type":"string"},"type":"array","title":"Same As","default":[]},"use_ai":{"type":"boolean","title":"Use Ai","default":false},"customer_id":{"type":"string","title":"Customer Id","default":""}},"type":"object","title":"EntityBuilderPlanRequest"},"EntityBuilderReviewRequest":{"properties":{"platform":{"type":"string","title":"Platform","default":""},"field":{"type":"string","title":"Field","default":""},"draft":{"type":"string","title":"Draft"},"context":{"additionalProperties":true,"type":"object","title":"Context","default":{}}},"type":"object","required":["draft"],"title":"EntityBuilderReviewRequest"},"FactLookup":{"properties":{"country":{"type":"string","title":"Country"},"company_id":{"type":"string","title":"Company Id"}},"type":"object","required":["country","company_id"],"title":"FactLookup"},"FeedbackBody":{"properties":{"conversation_id":{"type":"string","title":"Conversation Id"},"rating":{"type":"integer","title":"Rating"}},"type":"object","required":["conversation_id","rating"],"title":"FeedbackBody"},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"GateVerify":{"properties":{"claim":{"type":"string","title":"Claim"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","required":["claim"],"title":"GateVerify"},"GenerateBody":{"properties":{"position":{"type":"string","title":"Position","default":"hero"}},"type":"object","title":"GenerateBody"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImpactSimulateRequest":{"properties":{"action_ids":{"items":{"type":"string"},"type":"array","title":"Action Ids"}},"type":"object","required":["action_ids"],"title":"ImpactSimulateRequest"},"InstallerInvite":{"properties":{"email":{"type":"string","title":"Email"},"lang":{"type":"string","title":"Lang","default":""}},"type":"object","required":["email"],"title":"InstallerInvite"},"IntakeBody":{"properties":{"material":{"type":"string","title":"Material","default":""},"source_url":{"type":"string","title":"Source Url","default":""},"image_urls":{"items":{"type":"string"},"type":"array","title":"Image Urls","default":[]},"video_path":{"type":"string","title":"Video Path","default":""},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"IntakeBody"},"InvitationIn":{"properties":{"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id"},"name":{"type":"string","title":"Name","default":""},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"staff_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff Name"},"transaction_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Transaction At"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"consent":{"anyOf":[{"$ref":"#/components/schemas/ConsentIn"},{"type":"null"}]}},"type":"object","title":"InvitationIn"},"KeywordsConvert":{"properties":{"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords"}},"type":"object","required":["keywords"],"title":"KeywordsConvert"},"LLMSTXTRequest":{"properties":{"domain":{"type":"string","title":"Domain"},"brand_name":{"type":"string","title":"Brand Name","default":""},"description":{"type":"string","title":"Description","default":""},"services":{"items":{"type":"string"},"type":"array","title":"Services","default":[]},"location":{"type":"string","title":"Location","default":""},"usp":{"type":"string","title":"Usp","default":""},"customer_id":{"type":"string","title":"Customer Id","default":""},"lang":{"type":"string","title":"Lang","default":""},"country":{"type":"string","title":"Country","default":""}},"type":"object","required":["domain"],"title":"LLMSTXTRequest"},"LLMSTXTWebhookRequest":{"properties":{"customer_id":{"type":"string","title":"Customer Id","default":""},"domain":{"type":"string","title":"Domain","default":""},"event":{"type":"string","title":"Event","default":"content.updated"}},"type":"object","title":"LLMSTXTWebhookRequest"},"LiveConsent":{"properties":{"behavior":{"type":"boolean","title":"Behavior","default":false},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},"type":"object","title":"LiveConsent"},"LiveEvent":{"properties":{"type":{"type":"string","title":"Type"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"quantity":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quantity"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"}},"type":"object","required":["type"],"title":"LiveEvent"},"LiveEventsBody":{"properties":{"session_id":{"type":"string","title":"Session Id"},"consent":{"$ref":"#/components/schemas/LiveConsent"},"site_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Kind"},"lang":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"},"events":{"items":{"$ref":"#/components/schemas/LiveEvent"},"type":"array","title":"Events"}},"type":"object","required":["session_id"],"title":"LiveEventsBody"},"LiveSettingsBody":{"properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"budget_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Budget Pct"},"alert_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Alert Enabled"},"alert_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Alert Pct"},"alert_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alert Email"},"proactive_text":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Proactive Text"}},"type":"object","title":"LiveSettingsBody"},"LocationChoice":{"properties":{"location":{"type":"string","maxLength":200,"minLength":10,"title":"Location"}},"type":"object","required":["location"],"title":"LocationChoice"},"LocationIn":{"properties":{"name":{"type":"string","title":"Name"},"city":{"type":"string","title":"City","default":""},"country":{"type":"string","title":"Country","default":"dk"},"google_place_id":{"type":"string","title":"Google Place Id","default":""},"trustpilot_domain":{"type":"string","title":"Trustpilot Domain","default":""},"timezone":{"type":"string","title":"Timezone","default":"Europe/Copenhagen"},"booking_system_sends_own_requests":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Booking System Sends Own Requests"}},"type":"object","required":["name"],"title":"LocationIn","description":"Fladens feltnavne, ikke basens. Se _location_out for den anden vej."},"LoginRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"MagicLinkRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"MagicLinkRequest"},"MediaBody":{"properties":{"url":{"type":"string","title":"Url"},"alt":{"type":"string","title":"Alt","default":""},"position":{"type":"string","title":"Position","default":"hero"}},"type":"object","required":["url"],"title":"MediaBody"},"MemberRolePatch":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"role":{"type":"string","title":"Role"}},"type":"object","required":["customer_id","role"],"title":"MemberRolePatch"},"MentionAnalyzeRequest":{"properties":{"raw_response":{"type":"string","title":"Raw Response"},"brand_name":{"type":"string","title":"Brand Name"},"domain":{"type":"string","title":"Domain","default":""}},"type":"object","required":["raw_response","brand_name"],"title":"MentionAnalyzeRequest"},"Message":{"properties":{"role":{"type":"string","title":"Role"},"content":{"type":"string","title":"Content"}},"type":"object","required":["role","content"],"title":"Message"},"OnboardingConfirmation":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"brand_name":{"type":"string","title":"Brand Name","default":""},"brand_aliases":{"items":{"type":"string"},"type":"array","title":"Brand Aliases","default":[]},"sell":{"type":"string","title":"Sell","default":""},"area":{"type":"string","title":"Area","default":""},"buyer":{"type":"string","title":"Buyer","default":""},"remove_questions":{"items":{"type":"string"},"type":"array","title":"Remove Questions","default":[]},"remove_competitor_ids":{"items":{"type":"string"},"type":"array","title":"Remove Competitor Ids","default":[]},"services":{"items":{"type":"string"},"type":"array","title":"Services","default":[]}},"type":"object","required":["customer_id"],"title":"OnboardingConfirmation"},"OnboardingPatch":{"properties":{"state":{"additionalProperties":true,"type":"object","title":"State"}},"type":"object","required":["state"],"title":"OnboardingPatch"},"OptimizeRequest":{"properties":{"url":{"type":"string","maxLength":500,"minLength":8,"title":"Url"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"},"focus":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Focus"}},"type":"object","required":["url"],"title":"OptimizeRequest"},"OwnershipTransfer":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"OwnershipTransfer"},"PageContext":{"properties":{"route":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route"},"tab":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tab"},"customer_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Name"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Id"},"fix_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fix Id"}},"type":"object","title":"PageContext"},"PairingRequest":{"properties":{"product":{"type":"string","title":"Product","default":"wordpress"},"ttl_minutes":{"type":"integer","title":"Ttl Minutes","default":60}},"type":"object","title":"PairingRequest"},"PersonalizeRequest":{"properties":{"purchase":{"type":"string","maxLength":200,"minLength":3,"title":"Purchase"},"date":{"type":"string","maxLength":40,"title":"Date","default":""},"staff":{"type":"string","maxLength":40,"title":"Staff","default":""},"lang":{"type":"string","maxLength":5,"title":"Lang","default":""}},"type":"object","required":["purchase"],"title":"PersonalizeRequest"},"PlausibleConnectRequest":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"site_id":{"type":"string","title":"Site Id"},"api_key":{"type":"string","title":"Api Key"}},"type":"object","required":["customer_id","site_id","api_key"],"title":"PlausibleConnectRequest"},"PostIn":{"properties":{"summary":{"type":"string","maxLength":4000,"minLength":1,"title":"Summary"},"approved":{"type":"boolean","title":"Approved","default":false}},"type":"object","required":["summary"],"title":"PostIn"},"PostboxPatch":{"properties":{"alias":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alias"},"channels":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Channels"}},"type":"object","title":"PostboxPatch"},"PreviewIn":{"properties":{"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id"},"channel":{"type":"string","title":"Channel","default":"sms"},"recipient_name":{"type":"string","title":"Recipient Name","default":""},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","title":"PreviewIn"},"ProbeRequest":{"properties":{"industry_slug":{"type":"string","title":"Industry Slug"},"sample":{"type":"integer","title":"Sample","default":25}},"type":"object","required":["industry_slug"],"title":"ProbeRequest"},"ProfilePatch":{"properties":{"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"prefs":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Prefs"}},"type":"object","title":"ProfilePatch"},"PromptCreate":{"properties":{"prompt":{"type":"string","title":"Prompt"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","default":"manual"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","default":"da"}},"type":"object","required":["prompt"],"title":"PromptCreate"},"RecommendationStatusUpdate":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"id":{"type":"integer","title":"Id"},"status":{"type":"string","title":"Status"}},"type":"object","required":["customer_id","id","status"],"title":"RecommendationStatusUpdate"},"ResetPasswordRequest":{"properties":{"access_token":{"type":"string","title":"Access Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["access_token","new_password"],"title":"ResetPasswordRequest"},"ReviewConfirm":{"properties":{"confirmed":{"type":"boolean","title":"Confirmed"}},"type":"object","required":["confirmed"],"title":"ReviewConfirm"},"ReviewIn":{"properties":{"text":{"type":"string","maxLength":4000,"title":"Text","default":""},"rating":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Rating"},"author":{"type":"string","maxLength":120,"title":"Author","default":""},"lang":{"type":"string","maxLength":5,"title":"Lang","default":""}},"type":"object","title":"ReviewIn"},"ScanConfigPatch":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"config":{"additionalProperties":true,"type":"object","title":"Config"}},"type":"object","required":["customer_id","config"],"title":"ScanConfigPatch"},"ScanRequest":{"properties":{"industry_slug":{"type":"string","title":"Industry Slug"},"country":{"type":"string","title":"Country","default":"dk"},"cap":{"type":"integer","title":"Cap","default":150}},"type":"object","required":["industry_slug"],"title":"ScanRequest"},"SearchCreate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mode","default":"industry"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code","default":"dk"},"seed_input":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Seed Input"},"score_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score Cap","default":25}},"type":"object","title":"SearchCreate"},"SectionDeliver":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","title":"SectionDeliver"},"SeriesApprove":{"properties":{"reviewed":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Reviewed"}},"type":"object","title":"SeriesApprove"},"SeriesCreate":{"properties":{"name":{"type":"string","title":"Name"},"brief":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Brief"},"template":{"anyOf":[{"$ref":"#/components/schemas/SeriesTemplate"},{"type":"null"}]},"csv_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Csv Text"},"rows":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Rows"},"cities":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Cities"},"use_discovered_products":{"type":"boolean","title":"Use Discovered Products","default":false},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"source_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Label"},"review_target":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Review Target"},"drip_per_day":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Drip Per Day"},"series_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Series Type"}},"type":"object","required":["name"],"title":"SeriesCreate"},"SeriesDeliver":{"properties":{"content_ids":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Content Ids"}},"type":"object","title":"SeriesDeliver"},"SeriesGenerate":{"properties":{"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"},"force":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Force","default":false}},"type":"object","title":"SeriesGenerate"},"SeriesSchedule":{"properties":{"per_day":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Per Day"},"start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start"}},"type":"object","title":"SeriesSchedule"},"SeriesTemplate":{"properties":{"title_pattern":{"type":"string","title":"Title Pattern"},"slug_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug Pattern"},"meta_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Meta Pattern"},"seo_title_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Seo Title Pattern"},"subject_column":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Column"},"page_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Type"},"base_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Base Path"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","required":["title_pattern"],"title":"SeriesTemplate"},"SerpPinnedKeywords":{"properties":{"keywords":{"items":{"type":"string"},"type":"array","title":"Keywords"}},"type":"object","required":["keywords"],"title":"SerpPinnedKeywords"},"ShareTokenCreate":{"properties":{"expires_in_days":{"type":"integer","title":"Expires In Days","default":30},"label":{"type":"string","title":"Label","default":""}},"type":"object","title":"ShareTokenCreate"},"SignupRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"full_name":{"type":"string","title":"Full Name","default":""},"company_name":{"type":"string","title":"Company Name","default":""},"domain":{"type":"string","title":"Domain","default":""},"phone":{"type":"string","title":"Phone","default":""}},"type":"object","required":["email","password"],"title":"SignupRequest"},"StartScanAttach":{"properties":{"scan_id":{"type":"string","title":"Scan Id"}},"type":"object","required":["scan_id"],"title":"StartScanAttach"},"StudioPrefsUpdate":{"properties":{"figures":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Figures"}},"type":"object","title":"StudioPrefsUpdate"},"SupportBody":{"properties":{"message":{"type":"string","title":"Message"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"product":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product"},"customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Id"},"route":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"}},"type":"object","required":["message"],"title":"SupportBody"},"SuppressionIn":{"properties":{"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"location_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Id"}},"type":"object","title":"SuppressionIn"},"TeamInvite":{"properties":{"email":{"type":"string","title":"Email"},"customer_id":{"type":"string","title":"Customer Id"},"role":{"type":"string","title":"Role","default":"viewer"}},"type":"object","required":["email","customer_id"],"title":"TeamInvite"},"TokenBody":{"properties":{"token":{"type":"string","title":"Token"},"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"}},"type":"object","required":["token"],"title":"TokenBody"},"TrackEvent":{"properties":{"event":{"type":"string","title":"Event"},"properties":{"additionalProperties":true,"type":"object","title":"Properties","default":{}},"timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"}},"type":"object","required":["event"],"title":"TrackEvent"},"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"},"VerifyBody":{"properties":{"workspace_id":{"type":"string","title":"Workspace Id"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["workspace_id"],"title":"VerifyBody"},"VisitBody":{"properties":{"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"}},"type":"object","title":"VisitBody"},"VisitorSettingsBody":{"properties":{"enabled":{"type":"boolean","title":"Enabled"},"confirm_policy":{"type":"boolean","title":"Confirm Policy","default":false}},"type":"object","required":["enabled"],"title":"VisitorSettingsBody"},"WaitlistSignup":{"properties":{"email":{"type":"string","title":"Email"},"source":{"type":"string","title":"Source","default":""}},"type":"object","required":["email"],"title":"WaitlistSignup"},"WebhookBody":{"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"}},"type":"object","title":"WebhookBody"},"WorkspacePatch":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"slack_webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slack Webhook Url"},"slack_weekly_digest":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Slack Weekly Digest"},"slack_critical_alerts":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Slack Critical Alerts"},"lead_quota":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lead Quota"},"brand_settings":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Brand Settings"}},"type":"object","title":"WorkspacePatch"},"YouTubeContentRequest":{"properties":{"youtube_url":{"type":"string","title":"Youtube Url"},"customer_id":{"type":"string","title":"Customer Id"}},"type":"object","required":["youtube_url","customer_id"],"title":"YouTubeContentRequest"},"article_media__UploadUrlBody":{"properties":{"content_type":{"type":"string","title":"Content Type"},"kind":{"type":"string","title":"Kind","default":"image"}},"type":"object","required":["content_type"],"title":"UploadUrlBody"},"gbp_connect__ReplyIn":{"properties":{"comment":{"type":"string","maxLength":4096,"minLength":1,"title":"Comment"},"approved":{"type":"boolean","title":"Approved","default":false},"confirm_negative":{"type":"boolean","title":"Confirm Negative","default":false}},"type":"object","required":["comment"],"title":"ReplyIn"},"review_hub_google__ReplyIn":{"properties":{"text":{"type":"string","maxLength":4096,"minLength":1,"title":"Text"},"approved_by_user":{"type":"boolean","title":"Approved By User","default":false}},"type":"object","required":["text"],"title":"ReplyIn"},"social_repurpose__UploadUrlBody":{"properties":{"content_type":{"type":"string","title":"Content Type"},"kind":{"type":"string","title":"Kind","default":""}},"type":"object","required":["content_type"],"title":"UploadUrlBody"}}}}