{"openapi":"3.1.0","info":{"title":"VitalSentinel Public API","description":"The stable, versioned VitalSentinel API. Every operation is authenticated with a bearer credential - an API key or an OAuth 2.1 access token - sent in the `Authorization` header; session cookies are never accepted. Errors are RFC 9457 `application/problem+json`, so branch on the `type` URI rather than on the prose in `detail`. Routes outside this document are internal to the dashboard, carry no compatibility promise, and are not part of the contract.","version":"1.0.0"},"paths":{"/api/public/v1/alerts/history":{"get":{"tags":["Alerts"],"summary":"List fired alerts across every accessible domain","description":"Every fired alert (an in-app notification of type='alert') across every domain this credential can access, across every workspace it can access, newest first - the account-wide answer to 'what alerts have fired recently', which previously required naming a domain or looping every one returned by GET /domains and merging the results by hand. Pass workspace_id and/or domain_id to narrow instead. Each row carries its own domain_id/domain_name and workspace_id/workspace_name, so results never need correlating against a second call. Soft-deleted domains and soft-deleted workspaces are excluded, matching GET /domains exactly.","operationId":"list_alert_history_for_account_api_public_v1_alerts_history_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one workspace. Omit for every accessible workspace.","title":"Workspace Id"},"description":"Restrict to one workspace. Omit for every accessible workspace."},{"name":"domain_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one domain. Must be accessible to this credential.","title":"Domain Id"},"description":"Restrict to one domain. Must be accessible to this credential."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AlertHistoryPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/alerts/metrics":{"get":{"tags":["Alerts"],"summary":"List available alert metrics","description":"The metric catalogue: which metrics each monitoring module supports, their display unit, and default threshold/cooldown. Use this to validate a rule's module/metric pair before calling POST .../alerts/rules.","operationId":"get_available_metrics_api_public_v1_alerts_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailableMetricsResponsePublic"}}}}}}},"/api/public/v1/alerts/rules":{"get":{"tags":["Alerts"],"summary":"List alert rules across every accessible domain","description":"Every alert rule on every domain this credential can access, across every workspace it can access - the account-wide answer to 'what alert rules do I have set up', which previously required naming a domain or looping every one returned by GET /domains. Pass workspace_id to scope to one workspace instead of every accessible one. Ordered newest-created first, with a stable id tie-break for rules created in the same instant. Paginated like GET /alerts/history: pass limit/ cursor to see the rest once an account holds more rules than fit in one page. Soft-deleted domains and soft-deleted workspaces are excluded, matching GET /domains exactly.","operationId":"list_alert_rules_for_account_api_public_v1_alerts_rules_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one workspace. Omit for every accessible workspace.","title":"Workspace Id"},"description":"Restrict to one workspace. Omit for every accessible workspace."},{"name":"module","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/AlertModule"},{"type":"null"}],"description":"Filter by monitoring module.","title":"Module"},"description":"Filter by monitoring module."},{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by enabled status.","title":"Enabled"},"description":"Filter by enabled status."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AlertRulePublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/credits":{"get":{"tags":["Credits"],"summary":"Read the workspace's API credit balance","description":"Credits are the unit of public API and MCP consumption, charged per request against the workspace rather than against the credential. A cheap read costs 1, a heavy ClickHouse-backed read 5, and a job dispatch or a very wide pull 25. Discovery routes cost 0, an answer served from cache costs 0, and a response that is not 2xx is never charged - 'X-Credits-Cost' on every response is the authority on what a specific call cost.\n\nThe allowance refills on the workspace's billing anniversary and does not roll over. 'member' reports the caller's own cap where the workspace owner has set one, so a member who is blocked can tell that apart from a workspace that has run out. This endpoint itself is free and stays readable at a zero balance.\n\nCredits pay for API access, not for the underlying resource: an Inspector run started through the API still consumes the plan's Inspector quota as well.\n\nA request is charged to the workspace whose data it reads, so a connection covering several workspaces spends from several allowances. Pass 'workspace_id' to ask about a specific one; omit it and you get the connection's default workspace, which is the only one for an API key. 'X-Credits-Remaining' on any billable response reports the balance of the workspace that response was actually charged to.","operationId":"get_credit_balance_api_public_v1_credits_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Which workspace's allowance to report. Defaults to the connection's default workspace. Must be one this credential can see, or the call 404s.","title":"Workspace Id"},"description":"Which workspace's allowance to report. Defaults to the connection's default workspace. Must be one this credential can see, or the call 404s."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalance"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/credits/log":{"get":{"tags":["Credits"],"summary":"List this cycle's credit spending, request by request","description":"Every public API request the workspace was billed for in the current cycle, newest first, with what each one cost. This is what answers 'where did our credits go' - the balance endpoint holds running totals only.\n\nRows charged 0 are included and are the useful ones: a 0 on a route that normally costs 5 means the answer came from cache, and a 0 on a 4xx means a rejected request was not billed. Paths are route templates, never the resolved URL.\n\nThis is the API access log with prices attached rather than a separate billing record, so it inherits that log's 90-day retention and is not permanent. Export anything you need to keep beyond that window.\n\nRows are the ones this workspace PAID for. A request is charged to the workspace whose data it reads, so on a connection covering several workspaces each one has its own log - pass 'workspace_id' to read another, or omit it for the connection's default workspace.","operationId":"get_credit_log_api_public_v1_credits_log_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"description":"Rows per page. Defaults to 50, maximum 1000.","title":"Limit"},"description":"Rows per page. Defaults to 50, maximum 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass 'next_cursor' from the previous response verbatim.","title":"Cursor"},"description":"Pass 'next_cursor' from the previous response verbatim."},{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Whose spending to list. Defaults to the connection's default workspace. Must be one this credential can see, or the call 404s.","title":"Workspace Id"},"description":"Whose spending to list. Defaults to the connection's default workspace. Must be one this credential can see, or the call 404s."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_CreditLogEntry_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/credits/workspaces":{"get":{"tags":["Credits"],"summary":"Read every accessible workspace's own API credit balance","description":"One CreditBalance per workspace this credential can access - the answer to 'how many credits have I used' for a connection spanning several workspaces, none of which is invented here. The workspaces do not share a billing cycle, so there is no single 'used this cycle' figure to sum to: each item carries its own cycle_start/cycle_end alongside its own granted/used/remaining, and summing 'used' across items would blend different date ranges into a number that describes no real cycle. Soft-deleted workspaces are excluded. Returned as a single page today (next_cursor is always null). Priced and scoped the same as GET /credits - free, and no scope required, so it stays readable even when every workspace is at zero.","operationId":"list_credit_balances_api_public_v1_credits_workspaces_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_CreditBalance_"}}}}}}},"/api/public/v1/domains":{"get":{"tags":["Workspaces and domains"],"summary":"List domains","description":"Domains across every workspace this credential can access. This is the bounded, idempotent way to operate across all of a customer's domains: loop over this list rather than looking for a bulk endpoint, because the bulk fan-out endpoints in the internal API are deliberately not exposed here. Returned as a single page today (next_cursor is always null) - the envelope leaves room to paginate without a breaking change once that stops being true.","operationId":"list_domains_api_public_v1_domains_get","parameters":[{"name":"workspace_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one workspace. Omit for all accessible workspaces.","title":"Workspace Id"},"description":"Restrict to one workspace. Omit for all accessible workspaces."},{"name":"include_modules","in":"query","required":false,"schema":{"type":"boolean","description":"Include the active_modules field. Costs one extra query per module for the whole request, not per domain; set false when listing many domains and you only need identifiers.","default":true,"title":"Include Modules"},"description":"Include the active_modules field. Costs one extra query per module for the whole request, not per domain; set false when listing many domains and you only need identifiers."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_DomainSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/reports/default-config":{"get":{"tags":["Reports"],"summary":"Get the default sections_config for a new template","description":"A ready-to-use sections_config with every section enabled and each section's usual widgets on by default. Pass it straight through - optionally edited - as sections_config when creating a template.","operationId":"get_default_config_api_public_v1_domains_reports_default_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DefaultConfigPublic"}}}}}}},"/api/public/v1/domains/reports/sections-catalog":{"get":{"tags":["Reports"],"summary":"List report sections and widgets","description":"Every section and widget available for a report template's sections_config, with each widget's config_schema (field types, options, defaults). Static catalog data, not domain-scoped.","operationId":"get_sections_catalog_api_public_v1_domains_reports_sections_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_CatalogSectionPublic_"}}}}}}},"/api/public/v1/domains/{domain_id}":{"get":{"tags":["Workspaces and domains"],"summary":"Get a domain","operationId":"get_domain_api_public_v1_domains__domain_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/alerts/history":{"get":{"tags":["Notifications"],"summary":"List fired alerts for a domain","description":"Alert-history listing for one domain, newest first. Implemented as GET /notifications filtered to type='alert' and this domain - fired alerts surface to users as notifications rather than a separate history store, so this exists purely as an alerts-shaped convenience over that same data.","operationId":"list_alert_history_api_public_v1_domains__domain_id__alerts_history_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_NotificationPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/alerts/rules":{"get":{"tags":["Alerts"],"summary":"List alert rules for a domain","description":"Every alert rule configured on this domain, newest-created first (with a stable id tie-break for rules created in the same instant). Paginated like GET /alerts/rules: pass limit/cursor to see the rest once a domain holds more rules than fit in one page - a single domain can outgrow one page once rules are scoped per URL (crux, synthetic), not just per metric.","operationId":"list_alert_rules_api_public_v1_domains__domain_id__alerts_rules_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"module","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/AlertModule"},{"type":"null"}],"description":"Filter by monitoring module.","title":"Module"},"description":"Filter by monitoring module."},{"name":"enabled","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by enabled status.","title":"Enabled"},"description":"Filter by enabled status."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AlertRulePublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Alerts"],"summary":"Create an alert rule","description":"Create an alert rule on a domain. metric must be one of the values GET /alerts/metrics lists for the chosen module. pct_decrease/pct_increase/anomaly operators require comparison_window; every other operator requires threshold_value.","operationId":"create_alert_rule_api_public_v1_domains__domain_id__alerts_rules_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRuleCreatePublic"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRulePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/alerts/rules/{rule_id}":{"delete":{"tags":["Alerts"],"summary":"Delete an alert rule","operationId":"delete_alert_rule_api_public_v1_domains__domain_id__alerts_rules__rule_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Alerts"],"summary":"Get an alert rule","operationId":"get_alert_rule_api_public_v1_domains__domain_id__alerts_rules__rule_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRulePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Alerts"],"summary":"Update an alert rule","description":"Partial update - only the fields supplied are changed.","operationId":"update_alert_rule_api_public_v1_domains__domain_id__alerts_rules__rule_id__put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRuleUpdatePublic"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertRulePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/config":{"get":{"tags":["Web analytics"],"summary":"Get analytics configuration","description":"Display currency and the free-text storage switches for page titles and search terms.","operationId":"get_config_api_public_v1_domains__domain_id__analytics_config_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsConfigPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Web analytics"],"summary":"Update analytics configuration","description":"Only the fields present in the body are applied. Requires an admin/owner role.","operationId":"update_config_api_public_v1_domains__domain_id__analytics_config_put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsConfigUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsConfigPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/devices":{"get":{"tags":["Web analytics"],"summary":"Get device/browser/OS breakdown","description":"Visitors and sessions broken down by device type, browser, and OS. Set include_versions=true to also get per-version breakdowns and screen sizes (costs extra ClickHouse queries).","operationId":"get_devices_api_public_v1_domains__domain_id__analytics_devices_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"include_versions","in":"query","required":false,"schema":{"type":"boolean","description":"Include browser_versions/os_versions/screen_sizes.","default":false,"title":"Include Versions"},"description":"Include browser_versions/os_versions/screen_sizes."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicesPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/downloads":{"get":{"tags":["Web analytics"],"summary":"Get file download clicks","description":"Downloaded files (by extension match), with click and unique-visitor counts.","operationId":"get_downloads_api_public_v1_domains__domain_id__analytics_downloads_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":1000,"title":"Limit"},"description":"Page size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinksPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/ecommerce/overview":{"get":{"tags":["Web analytics"],"summary":"Get ecommerce overview","description":"Revenue, orders, and funnel counts (views/add-to-cart/checkout/purchase) for the window, plus a daily timeline. Revenue is in the domain's configured currency (GET .../config) and deduplicated by order id at the query layer.","operationId":"get_ecommerce_overview_api_public_v1_domains__domain_id__analytics_ecommerce_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"granularity","in":"query","required":false,"schema":{"enum":["hour","day","week"],"type":"string","description":"Timeline bucket size.","default":"day","title":"Granularity"},"description":"Timeline bucket size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcommerceOverviewPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/ecommerce/products":{"get":{"tags":["Web analytics"],"summary":"Get ecommerce products","description":"Per-product views, add-to-carts, purchases, revenue, and conversion rate for the window.","operationId":"get_ecommerce_products_api_public_v1_domains__domain_id__analytics_ecommerce_products_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50000,"minimum":1,"default":10000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EcommerceProductsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/events":{"get":{"tags":["Web analytics"],"summary":"Get custom events","description":"Custom event names fired via the tracking script's event API, with counts. event_name/properties are free text from the customer's own site instrumentation. An empty 'items' is a genuine zero, not a signal that tracking is broken - check data_through and events_ever_recorded before reporting 'no events'.","operationId":"get_events_api_public_v1_domains__domain_id__analytics_events_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"event_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Restrict to one event name, overriding the 'event' filter.","title":"Event Name"},"description":"Restrict to one event name, overriding the 'event' filter."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":1000,"title":"Limit"},"description":"Page size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/experiments":{"get":{"tags":["Web analytics"],"summary":"List experiments","description":"Persisted before/after experiments defined for this domain.","operationId":"list_experiments_api_public_v1_domains__domain_id__analytics_experiments_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ExperimentPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Web analytics"],"summary":"Create an experiment","description":"control_urls/variant_urls are normalized to path+query. pre_end_date must be before post_start_date. Requires an admin/owner role.","operationId":"create_experiment_api_public_v1_domains__domain_id__analytics_experiments_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/experiments/{experiment_id}":{"delete":{"tags":["Web analytics"],"summary":"Delete an experiment","description":"Requires an admin/owner role.","operationId":"delete_experiment_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Web analytics"],"summary":"Get an experiment","operationId":"get_experiment_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Web analytics"],"summary":"Update an experiment","description":"Only the fields present in the body are applied. Structural fields (URLs, dates, primary_metric, method) can only change while status is 'draft'. Requires an admin/owner role.","operationId":"update_experiment_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluate":{"post":{"tags":["Web analytics"],"summary":"Evaluate an experiment","description":"Computes effect size and significance between the pre- and post-period. Cheap methods (mann_whitney, did) run inline; bsts dispatches to a worker and returns a 'pending' evaluation to poll via GET .../evaluations/latest. Requires an admin/owner role and an Idempotency-Key header.","operationId":"evaluate_experiment_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__evaluate_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExperimentEvaluationPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluations":{"get":{"tags":["Web analytics"],"summary":"List evaluations","description":"Every evaluation ever run for this experiment, newest first.","operationId":"list_evaluations_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__evaluations_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ExperimentEvaluationPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/experiments/{experiment_id}/evaluations/latest":{"get":{"tags":["Web analytics"],"summary":"Get the latest evaluation","description":"The most recent evaluation row, or null if never evaluated. Does not trigger a new evaluation.","operationId":"get_latest_evaluation_api_public_v1_domains__domain_id__analytics_experiments__experiment_id__evaluations_latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ExperimentEvaluationPublic"},{"type":"null"}],"title":"Response Get Latest Evaluation Api Public V1 Domains  Domain Id  Analytics Experiments  Experiment Id  Evaluations Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/funnels":{"get":{"tags":["Web analytics"],"summary":"List funnels","description":"Persisted conversion funnels defined for this domain.","operationId":"list_funnels_api_public_v1_domains__domain_id__analytics_funnels_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_FunnelPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Web analytics"],"summary":"Create a funnel","description":"Steps are 2 to 5 ordered pageview/event matchers. Requires an admin/owner role.","operationId":"create_funnel_api_public_v1_domains__domain_id__analytics_funnels_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunnelCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunnelPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/funnels/{funnel_id}":{"delete":{"tags":["Web analytics"],"summary":"Delete a funnel","description":"Requires an admin/owner role.","operationId":"delete_funnel_api_public_v1_domains__domain_id__analytics_funnels__funnel_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"funnel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Funnel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Web analytics"],"summary":"Get a funnel","operationId":"get_funnel_api_public_v1_domains__domain_id__analytics_funnels__funnel_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"funnel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Funnel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunnelPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Web analytics"],"summary":"Update a funnel","description":"Only the fields present in the body are applied. Requires an admin/owner role.","operationId":"update_funnel_api_public_v1_domains__domain_id__analytics_funnels__funnel_id__put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"funnel_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Funnel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunnelUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunnelPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/geographic":{"get":{"tags":["Web analytics"],"summary":"Get geographic breakdown","description":"Visitors and sessions broken down by country, continent, and browser language.","operationId":"get_geographic_api_public_v1_domains__domain_id__analytics_geographic_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeographicPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/outbound":{"get":{"tags":["Web analytics"],"summary":"Get outbound link clicks","description":"External links clicked, with click and unique-visitor counts.","operationId":"get_outbound_api_public_v1_domains__domain_id__analytics_outbound_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":1000,"title":"Limit"},"description":"Page size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinksPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/overview":{"get":{"tags":["Web analytics"],"summary":"Get the traffic overview","description":"Key traffic metrics for the window (visitors, sessions, pageviews, bounce rate), top-5 pages/sources/countries, and *_change_pct fields comparing against the immediately preceding window of equal length - present without asking for them, e.g. for 'is bounce rate getting worse', but null when that preceding window's own total was zero, because a percent change against a zero baseline is undefined rather than 100%. bounce_rate_change_pp is the one exception: it is a rate, so its comparison is reported in percentage points, not *_change_pct - see that field's own description. Set compare_previous=true to also get the preceding window's own totals and exact start/end dates in 'previous'. Pass period='last_month' for the last COMPLETE calendar month rather than a trailing window - they are different questions with different answers - and with a calendar-month period compare_previous compares against the previous calendar month by default, not the preceding N days. Cached up to 10 minutes.","operationId":"get_overview_api_public_v1_domains__domain_id__analytics_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return the preceding window's own totals and exact start/end dates in 'previous'. The *_change_pct/bounce_rate_change_pp fields are present regardless of this flag; set it when a *_change_pct comes back null, which means that metric's baseline was zero, to see the baseline itself.","default":false,"title":"Compare Previous"},"description":"Also return the preceding window's own totals and exact start/end dates in 'previous'. The *_change_pct/bounce_rate_change_pp fields are present regardless of this flag; set it when a *_change_pct comes back null, which means that metric's baseline was zero, to see the baseline itself."},{"name":"compare_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(previous_period|calendar_month)$"},{"type":"null"}],"description":"Only used when compare_previous=true. 'previous_period' (the existing default): the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'compared to last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'.","title":"Compare Mode"},"description":"Only used when compare_previous=true. 'previous_period' (the existing default): the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'compared to last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverviewPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/pages":{"get":{"tags":["Web analytics"],"summary":"Get top, entry, or exit pages","description":"Pages ranked by traffic. type=all (default) ranks by pageviews with entries/exits/bounce_rate attached; type=entry ranks landing pages by session-entry count; type=exit ranks pages by session-exit count. sort_by only applies to type=all. A field the chosen type does not measure is null, not 0. Do not compare 'entries' across types: type=all reads the sessions table's recorded entry_page while type=entry re-derives the entry page from the events table, and on real data the two differ by more than a factor of two on the same page and window - see the field description before building a ranking on it.","operationId":"list_pages_api_public_v1_domains__domain_id__analytics_pages_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"type","in":"query","required":false,"schema":{"enum":["all","entry","exit"],"type":"string","description":"Which page ranking to return.","default":"all","title":"Type"},"description":"Which page ranking to return."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(pageviews|visitors|bounce_rate|avg_time)$","description":"Only used when type=all.","default":"pageviews","title":"Sort By"},"description":"Only used when type=all."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagesPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/realtime":{"get":{"tags":["Web analytics"],"summary":"Get live activity","description":"Active visitors and pages in roughly the last 5 minutes. Always live, never cached.","operationId":"get_realtime_api_public_v1_domains__domain_id__analytics_realtime_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RealtimePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/_registered":{"get":{"tags":["Web analytics"],"summary":"List registered report keys","description":"Every on-demand report key currently registered, its execution tier, and its freshness window in seconds.","operationId":"list_registered_api_public_v1_domains__domain_id__analytics_reports__registered_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisteredReportsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/_summary":{"get":{"tags":["Web analytics"],"summary":"Get the latest run per registered report","description":"One tile per registered report key: whether it has ever run, its latest status, and a small summary preview.","operationId":"get_summary_api_public_v1_domains__domain_id__analytics_reports__summary_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportSummaryPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/runs/{run_id}":{"get":{"tags":["Web analytics"],"summary":"Get a report run","description":"Status and (if completed) result of a specific report run.","operationId":"get_run_api_public_v1_domains__domain_id__analytics_reports_runs__run_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRunPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/runs/{run_id}/rows":{"get":{"tags":["Web analytics"],"summary":"Get a paginated slice of a report run's rows","description":"Server-side paginated slice of a completed run's result.rows, returned in items. Offset-based rather than cursor-based: the row set is a fixed snapshot from one run, not a continuously inserted table. search matches common text fields (query/page/path/url/name) case-insensitively.","operationId":"get_run_rows_api_public_v1_domains__domain_id__analytics_reports_runs__run_id__rows_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":25,"title":"Limit"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRowsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/{key}/latest":{"get":{"tags":["Web analytics"],"summary":"Get the latest completed run for a report","description":"Most recent completed run for this report key on this domain, or null if it has never completed.","operationId":"get_latest_run_api_public_v1_domains__domain_id__analytics_reports__key__latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ReportRunPublic"},{"type":"null"}],"title":"Response Get Latest Run Api Public V1 Domains  Domain Id  Analytics Reports  Key  Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/reports/{report_key}/run":{"post":{"tags":["Web analytics"],"summary":"Run an on-demand report","description":"Starts (or returns an already-fresh) computation for the named report. 'fast'-tier reports usually complete before this call returns; 'slow'/'ml'-tier reports return status='pending' or 'running' - poll GET .../reports/runs/{run_id} for completion. See GET .../reports/_registered for valid report_key values and their tier. Requires an Idempotency-Key header.","operationId":"run_report_api_public_v1_domains__domain_id__analytics_reports__report_key__run_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"report_key","in":"path","required":true,"schema":{"type":"string","title":"Report Key"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ReportRunRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportRunPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/search":{"get":{"tags":["Web analytics"],"summary":"Get site search queries","description":"Visitor-entered site-search queries, with search and unique-visitor counts. query is visitor free text.","operationId":"get_search_api_public_v1_domains__domain_id__analytics_search_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":1000,"title":"Limit"},"description":"Page size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/sources":{"get":{"tags":["Web analytics"],"summary":"Get traffic source breakdown","description":"Visitors and sessions broken down by source, medium, campaign, channel, and referrer domain.","operationId":"get_sources_api_public_v1_domains__domain_id__analytics_sources_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourcesPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/analytics/timeline":{"get":{"tags":["Web analytics"],"summary":"Get the visitors/pageviews timeline","description":"Visitor, session, and pageview counts bucketed by hour, day, or week over the window.","operationId":"get_timeline_api_public_v1_domains__domain_id__analytics_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"granularity","in":"query","required":false,"schema":{"enum":["hour","day","week"],"type":"string","description":"Bucket size.","default":"day","title":"Granularity"},"description":"Bucket size."},{"name":"period_days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Window size in days, ending now.","default":30,"title":"Period Days"},"description":"Window size in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days.","title":"Start Date"},"description":"Window start 'YYYY-MM-DD' UTC. Overrides period_days."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now.","title":"End Date"},"description":"Window end 'YYYY-MM-DD' UTC, inclusive. Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: traffic source.","title":"Source"},"description":"Filter: traffic source."},{"name":"medium","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM medium.","title":"Medium"},"description":"Filter: UTM medium."},{"name":"campaign","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: UTM campaign.","title":"Campaign"},"description":"Filter: UTM campaign."},{"name":"channel","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: channel grouping.","title":"Channel"},"description":"Filter: channel grouping."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: ISO country code.","title":"Country"},"description":"Filter: ISO country code."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: device type.","title":"Device"},"description":"Filter: device type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: browser.","title":"Browser"},"description":"Filter: browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: OS.","title":"Os"},"description":"Filter: OS."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: page path viewed in the session.","title":"Page"},"description":"Filter: page path viewed in the session."},{"name":"referrer","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: referrer domain.","title":"Referrer"},"description":"Filter: referrer domain."},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: custom event fired in the session.","title":"Event"},"description":"Filter: custom event fired in the session."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelinePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/compare":{"get":{"tags":["CrUX"],"summary":"Compare CrUX metrics between two periods","description":"Average 75th-percentile values for every metric across two date ranges, plus the percent change between them.","operationId":"compare_crux_periods_api_public_v1_domains__domain_id__crux_compare_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period1_start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Period1 Start"}},{"name":"period1_end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Period1 End"}},{"name":"period2_start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Period2 Start"}},{"name":"period2_end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Period2 End"}},{"name":"form_factor","in":"query","required":true,"schema":{"type":"string","pattern":"^(PHONE|DESKTOP)$","title":"Form Factor"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxCompareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/histogram/{metric_name}":{"get":{"tags":["CrUX"],"summary":"Get the distribution histogram for one CrUX metric","description":"Bucketed distribution of measurements for one metric and form factor on one collection date (or the latest, if collection_date is omitted). Densities sum to approximately 1.","operationId":"get_crux_metric_histogram_api_public_v1_domains__domain_id__crux_histogram__metric_name__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"metric_name","in":"path","required":true,"schema":{"type":"string","pattern":"^(largest_contentful_paint|first_contentful_paint|interaction_to_next_paint|cumulative_layout_shift|experimental_time_to_first_byte)$","title":"Metric Name"}},{"name":"form_factor","in":"query","required":true,"schema":{"type":"string","pattern":"^(PHONE|DESKTOP)$","title":"Form Factor"}},{"name":"collection_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Defaults to the latest collection.","title":"Collection Date"},"description":"Defaults to the latest collection."},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxHistogramResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/overview":{"get":{"tags":["CrUX"],"summary":"Get the latest CrUX overview","description":"Latest Core Web Vitals across every form factor, as of the most recent weekly collection. Pass url_id to scope to one monitored URL; omit for the domain origin. CrUX field data reflects real Chrome users over a rolling 28-day window as reported by Google, so a value can lag a real-world change by up to a month.","operationId":"get_crux_overview_api_public_v1_domains__domain_id__crux_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/timeline":{"get":{"tags":["CrUX"],"summary":"Get the CrUX metrics timeline","description":"Historical Core Web Vitals by weekly collection date. With no dates and no period, defaults to the last 90 days (about 12 weekly collections, which is a readable trend); pass explicit dates or period for a longer one, up to a cap of 36 months of history. Pass period='last_month' for the last COMPLETE calendar month - a different window from the trailing 30 days, not a synonym for it. The window actually queried is always echoed as start_date/end_date. Pass url_id to scope to one monitored URL. **Pass metric_name when you want one metric's trend.** CrUX carries far more than the five headline vitals - LCP sub-parts, resource types and form-factor shares each arrive as their own metric_name - so an unfiltered window is 16 metrics multiplied by 3 form factors multiplied by every collection date, and a consumer with a size budget ends up with a couple of dates per metric rather than a trend for the one that was asked about. metric_names_available lists what is present in the window.","operationId":"get_crux_timeline_api_public_v1_domains__domain_id__crux_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Defaults to 90 days before end_date.","title":"Start Date"},"description":"Defaults to 90 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Defaults to today.","title":"End Date"},"description":"Defaults to today."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"form_factor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(PHONE|DESKTOP|ALL)$"},{"type":"null"}],"title":"Form Factor"}},{"name":"metric_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[a-z0-9_]{1,80}$"},{"type":"null"}],"description":"Return only this metric's points, e.g. largest_contentful_paint. Matches the metric_name values the rows carry, which metric_names_available always lists in full regardless of this filter. An unrecognised name returns no items rather than an error, with metric_names_available naming the alternatives.","title":"Metric Name"},"description":"Return only this metric's points, e.g. largest_contentful_paint. Matches the metric_name values the rows carry, which metric_names_available always lists in full regardless of this filter. An unrecognised name returns no items rather than an error, with metric_names_available naming the alternatives."},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxTimelineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/trends/{metric_name}":{"get":{"tags":["CrUX"],"summary":"Get the trend for one CrUX metric","description":"75th-percentile trend for a single metric, comparing the first and last collection dates in the window. metric_name is one of largest_contentful_paint, first_contentful_paint, interaction_to_next_paint, cumulative_layout_shift, experimental_time_to_first_byte.","operationId":"get_crux_metric_trend_api_public_v1_domains__domain_id__crux_trends__metric_name__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"metric_name","in":"path","required":true,"schema":{"type":"string","pattern":"^(largest_contentful_paint|first_contentful_paint|interaction_to_next_paint|cumulative_layout_shift|experimental_time_to_first_byte)$","title":"Metric Name"}},{"name":"form_factor","in":"query","required":true,"schema":{"type":"string","pattern":"^(PHONE|DESKTOP)$","title":"Form Factor"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Defaults to 180 days before end_date.","title":"Start Date"},"description":"Defaults to 180 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Defaults to today.","title":"End Date"},"description":"Defaults to today."},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxTrendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/urls":{"get":{"tags":["CrUX"],"summary":"List monitored CrUX URLs","description":"URLs currently tracked for Core Web Vitals field data, cursor-paginated. has_data=true (the default) restricts the list to URLs with at least one collected metric; pass false to see the full configuration list, including URLs still waiting on their first weekly collection.","operationId":"list_crux_urls_api_public_v1_domains__domain_id__crux_urls_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size.","default":25,"title":"Limit"},"description":"Page size."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive substring match on the URL.","title":"Search"},"description":"Case-insensitive substring match on the URL."},{"name":"has_data","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Has Data"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_CruxUrlPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/urls/bulk":{"post":{"tags":["CrUX"],"summary":"Add multiple URLs to CrUX monitoring","description":"Adds up to 200 URLs in one call. Fills the workspace's remaining plan slots and reports the rest as per-URL errors rather than rejecting the whole request - if 30 slots remain and you send 50 URLs, the first 30 in list order are added.","operationId":"bulk_add_crux_urls_api_public_v1_domains__domain_id__crux_urls_bulk_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxUrlBulkAddRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxUrlBulkAddResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/urls/bulk-delete":{"post":{"tags":["CrUX"],"summary":"Remove multiple URLs from CrUX monitoring","description":"Deletes up to 200 URLs and their collected field-data history in one call. Origin URLs cannot be removed and are reported as per-id errors.","operationId":"bulk_delete_crux_urls_api_public_v1_domains__domain_id__crux_urls_bulk_delete_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxUrlBulkDeleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxUrlBulkDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/crux/urls/{url_id}":{"delete":{"tags":["CrUX"],"summary":"Remove a URL from CrUX monitoring","description":"Deletes the URL and its collected field-data history. The origin URL cannot be removed.","operationId":"delete_crux_url_api_public_v1_domains__domain_id__crux_urls__url_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["CrUX"],"summary":"Get a monitored CrUX URL","operationId":"get_crux_url_api_public_v1_domains__domain_id__crux_urls__url_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CruxUrlPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/accounts":{"get":{"tags":["Google Analytics"],"summary":"List Google accounts reachable from this domain's workspace","description":"Connected Google accounts whose GA properties are reachable from this domain's workspace, including accounts connected from another workspace with a property linked into this one. Check token_valid before relying on fresh collection.","operationId":"list_accounts_api_public_v1_domains__domain_id__ga_accounts_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GAAccountPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/dimensions":{"get":{"tags":["Google Analytics"],"summary":"Get GA4 dimension breakdowns for this domain","description":"Period-level GA4 dimension totals (e.g. traffic source, device category) straight from the GA4 API, not summed from daily ClickHouse rows. 'period_dimension_total_counts' gives the true count of distinct values per dimension independent of 'limit', so a client can show 'N of M'. Check 'account_token_valid' before relying on these totals - a revoked Google grant leaves the last-collected figures in place rather than an error.","operationId":"get_dimensions_api_public_v1_domains__domain_id__ga_dimensions_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default today.","title":"End Date"},"description":"YYYY-MM-DD, default today."},{"name":"compare_start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison start.","title":"Compare Start Date"},"description":"YYYY-MM-DD comparison start."},{"name":"compare_end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison end.","title":"Compare End Date"},"description":"YYYY-MM-DD comparison end."},{"name":"dimension_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single dimension.","title":"Dimension Name"},"description":"Filter to a single dimension."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":1,"description":"Max values returned per dimension type.","default":100,"title":"Limit"},"description":"Max values returned per dimension type."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset within each dimension type.","default":0,"title":"Offset"},"description":"Pagination offset within each dimension type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GADimensionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/events":{"get":{"tags":["Google Analytics"],"summary":"Get GA4 custom events for this domain","description":"Daily GA4 event counts collected into ClickHouse (default range: 30 days). Pass 'event_name' to filter to one event; omit it for every event GA4 reports.","operationId":"get_events_api_public_v1_domains__domain_id__ga_events_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default today.","title":"End Date"},"description":"YYYY-MM-DD, default today."},{"name":"compare_start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison start.","title":"Compare Start Date"},"description":"YYYY-MM-DD comparison start."},{"name":"compare_end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison end.","title":"Compare End Date"},"description":"YYYY-MM-DD comparison end."},{"name":"event_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single GA4 event name.","title":"Event Name"},"description":"Filter to a single GA4 event name."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":1,"default":10000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GAEventsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/metrics":{"get":{"tags":["Google Analytics"],"summary":"Get GA4 metrics for this domain","description":"Daily GA4 metric values collected into ClickHouse (default range: 30 days, max: 36 months back). Pass 'metric_name' to filter to one metric; omit it for every metric GA4 reports. Pass compare_start_date/compare_end_date for a second period to diff against.","operationId":"get_metrics_api_public_v1_domains__domain_id__ga_metrics_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default today.","title":"End Date"},"description":"YYYY-MM-DD, default today."},{"name":"compare_start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison start.","title":"Compare Start Date"},"description":"YYYY-MM-DD comparison start."},{"name":"compare_end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison end.","title":"Compare End Date"},"description":"YYYY-MM-DD comparison end."},{"name":"metric_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to a single GA4 metric name.","title":"Metric Name"},"description":"Filter to a single GA4 metric name."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":1,"default":10000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GAMetricsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/properties/all":{"get":{"tags":["Google Analytics"],"summary":"List GA properties available to this domain's workspace","description":"Every GA4 property reachable from the domain's workspace across all connected Google accounts, whether or not it is linked to a domain yet, in 'items'. Page-numbered via 'page'/'per_page'/'total_pages', not cursor-paginated - the underlying set is a small connected-account list, not a continuously growing table - so 'next_cursor' is always null.","operationId":"list_workspace_properties_api_public_v1_domains__domain_id__ga_properties_all_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Per Page"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by property name, ID, URL, or account email.","title":"Search"},"description":"Filter by property name, ID, URL, or account email."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GAPropertiesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/properties/linked":{"get":{"tags":["Google Analytics"],"summary":"Get the GA property linked to this domain","description":"The GA4 property configured for this domain, or null if none is linked.","operationId":"get_linked_property_api_public_v1_domains__domain_id__ga_properties_linked_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GAPropertyPublic"},{"type":"null"}],"title":"Response Get Linked Property Api Public V1 Domains  Domain Id  Ga Properties Linked Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ga/timeline":{"get":{"tags":["Google Analytics"],"summary":"Get the GA4 traffic timeline for this domain","description":"Daily users/sessions/engagement series for this domain's GA4 property, plus period-level totals from the GA4 API (more accurate than summing the daily rows). Pass compare_start_date/compare_end_date for a second period to diff against. Check 'account_token_valid' before relying on these totals - a revoked Google grant leaves the last-collected figures in place rather than an error.","operationId":"get_timeline_api_public_v1_domains__domain_id__ga_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default today.","title":"End Date"},"description":"YYYY-MM-DD, default today."},{"name":"compare_start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison start.","title":"Compare Start Date"},"description":"YYYY-MM-DD comparison start."},{"name":"compare_end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD comparison end.","title":"Compare End Date"},"description":"YYYY-MM-DD comparison end."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GATimelinePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/brand-patterns":{"get":{"tags":["Search Console"],"summary":"List brand-query patterns for this domain","description":"Query tokens configured as 'brand' for CTR-curve segmentation and topic-cluster intent labelling.","operationId":"list_brand_patterns_api_public_v1_domains__domain_id__gsc_brand_patterns_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GSCBrandPatternResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/countries":{"get":{"tags":["Search Console"],"summary":"Get GSC performance broken down by country","description":"Clicks/impressions/CTR/position per country (default: 28 days, the same window GET .../gsc/overview uses so the rows reconcile with the property total; the window covered is echoed as range_start/range_end). Offset-paginated - not cursor-paginated, so 'next_cursor' is always null; page further with 'offset'. When filtered to a single query with no page filter, reads from the page-free dimensional table so impressions are not double-counted across the pages a query can surface multiple URLs for.","operationId":"get_countries_api_public_v1_domains__domain_id__gsc_countries_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 28 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 28 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCCountriesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/ctr-benchmark":{"get":{"tags":["Search Console"],"summary":"Is this domain's CTR good, benchmarked against its own history or industry reference","description":"Impression-weighted actual CTR against the impression-weighted expected CTR across this domain's real per-position distribution (default: trailing 28 days), and the gap between them - answers 'is my CTR good' without a report run. The window is clamped to the property's real data_through, never past it - see data_through and range_end below, which state the window that actually produced the numbers, not necessarily the one requested. actual_ctr, expected_ctr and average_position are all computed over the SAME restricted population (position 1-20, >=10 impressions per query/page/device group) so none of them ever mixes a bucket-scoped figure with an unrestricted one, and all three are null together when no bucket in the window qualifies rather than reporting a zero CTR nobody measured; property_ctr is the unrestricted, property-wide CTR /gsc/overview reports, and bucket_coverage_pct states what share of the window's traffic the compared buckets actually cover - the explanation for why actual_ctr and property_ctr can differ. Prefers this property's own history-fitted CTR-vs-position curve (source='fitted_curve'); falls back to the quarterly AWR industry reference (source='industry_benchmark') when no fitted curve is cached yet, or to 'none' when neither is available. Read-only: does not enqueue a report, does not write to the curve cache, and costs no more than any other light GSC read.","operationId":"get_ctr_benchmark_api_public_v1_domains__domain_id__gsc_ctr_benchmark_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 28 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 28 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCCTRBenchmarkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/devices":{"get":{"tags":["Search Console"],"summary":"Get GSC performance broken down by device","description":"Clicks/impressions/CTR/position per device category (default: 28 days, the same window GET .../gsc/overview uses, so the rows reconcile with the property total). The window covered is echoed as range_start/range_end.","operationId":"get_devices_api_public_v1_domains__domain_id__gsc_devices_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 28 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 28 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCDevicesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/experiments":{"get":{"tags":["Search Console"],"summary":"List experiments for this domain","operationId":"list_experiments_api_public_v1_domains__domain_id__gsc_experiments_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GSCExperimentResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Search Console"],"summary":"Create an A/B experiment for this domain","description":"Defines a control/variant URL split and a pre/post date window for statistical comparison. pre_start_date must fall within GSC's 16-month retention window.","operationId":"create_experiment_api_public_v1_domains__domain_id__gsc_experiments_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCExperimentCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCExperimentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/experiments/{experiment_id}":{"delete":{"tags":["Search Console"],"summary":"Delete an experiment","operationId":"delete_experiment_api_public_v1_domains__domain_id__gsc_experiments__experiment_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Search Console"],"summary":"Update an experiment","description":"Once an experiment leaves 'draft', method, primary_metric, the URL lists, and the pre-period dates are frozen - create a new experiment to change them.","operationId":"update_experiment_api_public_v1_domains__domain_id__gsc_experiments__experiment_id__put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCExperimentCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCExperimentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/experiments/{experiment_id}/evaluate":{"post":{"tags":["Search Console"],"summary":"Evaluate an experiment","description":"Runs the configured statistical test (Mann-Whitney/DiD run inline; BSTS is queued to the ML worker). Poll poll_url for the result. Requires an Idempotency-Key header so a retry does not queue a second BSTS job.","operationId":"evaluate_experiment_api_public_v1_domains__domain_id__gsc_experiments__experiment_id__evaluate_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/experiments/{experiment_id}/evaluations/latest":{"get":{"tags":["Search Console"],"summary":"Get the latest evaluation for an experiment","description":"Poll this after POSTing .../evaluate. Null if the experiment has never been evaluated.","operationId":"get_latest_evaluation_api_public_v1_domains__domain_id__gsc_experiments__experiment_id__evaluations_latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"experiment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Experiment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GSCExperimentEvaluationResponse"},{"type":"null"}],"title":"Response Get Latest Evaluation Api Public V1 Domains  Domain Id  Gsc Experiments  Experiment Id  Evaluations Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/known-events":{"get":{"tags":["Search Console"],"summary":"List known events annotated on this domain's traffic","description":"Domain-scoped events plus every global event (domain_id null), used to annotate traffic-anomaly charts.","operationId":"list_known_events_api_public_v1_domains__domain_id__gsc_known_events_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GSCKnownEventResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/locale-config":{"get":{"tags":["Search Console"],"summary":"Get the locale-detection config for this domain","description":"Null if unconfigured - locale detection then falls back to auto-detection.","operationId":"get_locale_config_api_public_v1_domains__domain_id__gsc_locale_config_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GSCLocaleConfig"},{"type":"null"}],"title":"Response Get Locale Config Api Public V1 Domains  Domain Id  Gsc Locale Config Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/new-rankings":{"get":{"tags":["Search Console"],"summary":"Get queries or pages new to this domain's rankings","description":"Queries or pages (per 'type') with at least one impression in the requested range and zero in the immediately preceding range of the same length - i.e. 'new since last period', not 'never seen before'. Offset-paginated, not cursor-paginated, so 'next_cursor' is always null; page further with 'offset'.","operationId":"get_new_rankings_api_public_v1_domains__domain_id__gsc_new_rankings_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"type","in":"query","required":false,"schema":{"type":"string","description":"'queries' or 'pages'.","default":"queries","title":"Type"},"description":"'queries' or 'pages'."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCNewRankingsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/overview":{"get":{"tags":["Search Console"],"summary":"Get GSC overview statistics for this domain","description":"Total clicks, impressions, average CTR and position for the range (default: trailing 28 days), plus distinct query and page counts. Backed by ClickHouse; GSC itself has a 2-3 day publish delay so the most recent days will be thin. Pass period='last_month' for the last COMPLETE calendar month rather than a trailing window - they are different questions with different answers - and with a calendar-month period compare_previous compares against the previous calendar month, not against the preceding N days.","operationId":"get_overview_api_public_v1_domains__domain_id__gsc_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 28 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 28 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"compare_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(previous_period|calendar_month)$"},{"type":"null"}],"description":"Only used when compare_previous=true. 'previous_period': the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'versus last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'.","title":"Compare Mode"},"description":"Only used when compare_previous=true. 'previous_period': the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'versus last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also fetch the immediately preceding window of equal length and return its totals plus the percentage change for clicks, impressions, ctr and position. Costs a second internal call. Position is inverted - LOWER is better - so a NEGATIVE average_position_change_pct is an improvement, not a regression.","default":false,"title":"Compare Previous"},"description":"Also fetch the immediately preceding window of equal length and return its totals plus the percentage change for clicks, impressions, ctr and position. Costs a second internal call. Position is inverted - LOWER is better - so a NEGATIVE average_position_change_pct is an improvement, not a regression."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/pages":{"get":{"tags":["Search Console"],"summary":"Get page-level GSC performance for this domain","description":"Clicks/impressions/CTR/position per landing page, ranked by clicks descending (default: 30 days, offset-paginated - not cursor-paginated, so 'next_cursor' is always null; page further with 'offset'). Filter to a query/country/device to see which pages captured that slice's traffic. sort_by can rank by period-over-period change (which pages lost the most search traffic), and declining_only filters to just the losers; either one turns compare_previous on by itself - but note that both select from pages ranked by CURRENT clicks, so a page that fell to zero clicks is not in the candidate set at any limit and can never appear. Pass period='last_month' for the last COMPLETE calendar month rather than a trailing window: 'last month' and 'last 30 days' are different questions with different answers, and with a calendar-month period compare_previous compares against the previous calendar month, not the preceding N days.","operationId":"get_pages_api_public_v1_domains__domain_id__gsc_pages_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"compare_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(previous_period|calendar_month)$"},{"type":"null"}],"description":"Only used when a comparison runs. 'previous_period': the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'versus last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period' - the 31 days before July are not June.","title":"Compare Mode"},"description":"Only used when a comparison runs. 'previous_period': the immediately preceding window of equal length. 'calendar_month': the full calendar month before the month this window ends in, which is what 'versus last month' means. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period' - the 31 days before July are not June."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return each page's clicks/impressions/ctr/position for the immediately preceding window of equal length, plus the percentage change (prev_* and *_change_pct fields) - this is how to find pages that lost search traffic. Costs a second internal call, ranked by clicks, so coverage is best for the busiest pages - a low-traffic page may show a null comparison even if it had data last period. Position is inverted - LOWER is better - so a NEGATIVE position_change_pct is an improvement, not a regression.","default":false,"title":"Compare Previous"},"description":"Also return each page's clicks/impressions/ctr/position for the immediately preceding window of equal length, plus the percentage change (prev_* and *_change_pct fields) - this is how to find pages that lost search traffic. Costs a second internal call, ranked by clicks, so coverage is best for the busiest pages - a low-traffic page may show a null comparison even if it had data last period. Position is inverted - LOWER is better - so a NEGATIVE position_change_pct is an improvement, not a regression."},{"name":"narrow_to_synced","in":"query","required":false,"schema":{"type":"boolean","description":"Opt in to cutting the window back to the newest date that actually has synced Search Console data, instead of returning a window whose last 2-3 days are empty because of GSC's publish lag. Off by default: it changes the window you asked for, and the unnarrowed answer is the honest one when you did not ask. When it applies, range_start/range_end report the window ACTUALLY used - always read them rather than assuming your own dates - and comparison_coverage comes back like_for_like instead of carrying a notice telling you to retry with a narrower end_date. Under a calendar-month comparison the baseline month is truncated to the same number of days, so a partial July is compared against the same span of June rather than against all of it; comparison_coverage.previous_days_requested reports the result. A no-op when the window already ends at or before the synced date, and when nothing is synced at all.","default":false,"title":"Narrow To Synced"},"description":"Opt in to cutting the window back to the newest date that actually has synced Search Console data, instead of returning a window whose last 2-3 days are empty because of GSC's publish lag. Off by default: it changes the window you asked for, and the unnarrowed answer is the honest one when you did not ask. When it applies, range_start/range_end report the window ACTUALLY used - always read them rather than assuming your own dates - and comparison_coverage comes back like_for_like instead of carrying a notice telling you to retry with a narrower end_date. Under a calendar-month comparison the baseline month is truncated to the same number of days, so a partial July is compared against the same span of June rather than against all of it; comparison_coverage.previous_days_requested reports the result. A no-op when the window already ends at or before the synced date, and when nothing is synced at all."},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(clicks_change_pct|impressions_change_pct|ctr_change_pct|position_change_pct)$"},{"type":"null"}],"description":"Server-side ordering by period-over-period change - turns compare_previous on by itself, so you do not need to pass both. Ranks ONLY within the already-fetched page, never across the whole property (ranking_scope='fetched_page' in the response - see that field's own description) - raising 'limit' widens the candidate set the ranking runs over and can change which row comes out on top. clicks_change_pct/impressions_change_pct/ctr_change_pct sort most-declined-first (most negative change first); position_change_pct sorts most-worsened-first (position is inverted - a POSITIVE change is the regression). A page with no previous-window match always sorts last. Because the candidate set is always selected by CURRENT clicks, a page that fell to zero clicks this window can never surface here at any limit, even if it is the property's single biggest decliner - inherent to the design, not a bug. Omit to keep the default order (ranked by current clicks, ranking_scope='property').","title":"Sort By"},"description":"Server-side ordering by period-over-period change - turns compare_previous on by itself, so you do not need to pass both. Ranks ONLY within the already-fetched page, never across the whole property (ranking_scope='fetched_page' in the response - see that field's own description) - raising 'limit' widens the candidate set the ranking runs over and can change which row comes out on top. clicks_change_pct/impressions_change_pct/ctr_change_pct sort most-declined-first (most negative change first); position_change_pct sorts most-worsened-first (position is inverted - a POSITIVE change is the regression). A page with no previous-window match always sorts last. Because the candidate set is always selected by CURRENT clicks, a page that fell to zero clicks this window can never surface here at any limit, even if it is the property's single biggest decliner - inherent to the design, not a bug. Omit to keep the default order (ranked by current clicks, ranking_scope='property')."},{"name":"declining_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only rows whose clicks fell vs the immediately preceding window (clicks_change_pct < 0). Turns compare_previous on by itself, so you do not need to pass both. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs, since the true count of declining rows beyond this page is unknown - see has_more. The candidate set itself is always selected by CURRENT clicks, so a row that fell all the way to zero clicks this window was never fetched to begin with and can never appear here, even though it lost 100% of its traffic.","default":false,"title":"Declining Only"},"description":"Only rows whose clicks fell vs the immediately preceding window (clicks_change_pct < 0). Turns compare_previous on by itself, so you do not need to pass both. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs, since the true count of declining rows beyond this page is unknown - see has_more. The candidate set itself is always selected by CURRENT clicks, so a row that fell all the way to zero clicks this window was never fetched to begin with and can never appear here, even though it lost 100% of its traffic."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":1000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCPagesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/performance":{"get":{"tags":["Search Console"],"summary":"Get the GSC performance time series for this domain","description":"Daily clicks/impressions/CTR/position (default: 30 days, max: 16 months - GSC's own retention limit), one point per day in 'items'. Filter to a single query/page/country/device to see that slice's daily trend instead of the whole property.","operationId":"get_performance_api_public_v1_domains__domain_id__gsc_performance_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":1,"default":10000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCPerformancePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/queries":{"get":{"tags":["Search Console"],"summary":"Get query-level GSC performance for this domain","description":"Clicks/impressions/CTR/position per search query (default: 30 days, offset-paginated - not cursor-paginated, so 'next_cursor' is always null; page further with 'offset'). Filter to a page/country/device to see which queries drove traffic to that slice. 'sort_by' ranks server-side across the whole property, so high-impression/low-click queries and best-CTR queries are reachable without paging the entire set; 'query_contains' matches a substring, which the exact-match 'query' filter cannot, and 'position_min'/'position_max' select a ranking band. Every row carries is_brand, and 'brand' filters to just one side - see brand_patterns_configured when nothing is classified as brand. 'brand_totals' answers 'how much of my search traffic is branded' for the whole window in one field; do not hand-sum is_brand over the rows, which describes only the page you fetched. When a response-side filter (brand, declining_only) narrows the page, resume with 'next_offset' rather than advancing 'offset' by the number of rows you received.","operationId":"get_queries_api_public_v1_domains__domain_id__gsc_queries_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."},{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one query.","title":"Query"},"description":"Filter to one query."},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one page URL.","title":"Page"},"description":"Filter to one page URL."},{"name":"country","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works).","title":"Country"},"description":"Filter to one country, as the ISO-3166-1 alpha-3 code get_search_countries returns (for example 'fra' or 'FRA' - either case works)."},{"name":"device","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive.","title":"Device"},"description":"Filter to one device: DESKTOP, MOBILE, or TABLET. Case-insensitive."},{"name":"search_type","in":"query","required":false,"schema":{"type":"string","description":"web, image, video, or news.","default":"web","title":"Search Type"},"description":"web, image, video, or news."},{"name":"query_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive substring match on the query text.","title":"Query Contains"},"description":"Case-insensitive substring match on the query text."},{"name":"brand","in":"query","required":false,"schema":{"anyOf":[{"enum":["brand","nonbrand"],"type":"string"},{"type":"null"}],"description":"Only 'brand' or 'nonbrand' queries. Every row also carries is_brand regardless of this filter. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs - page on with next_offset, not by adding len(items) to your own offset. For 'how much of my traffic is branded' read brand_totals, which covers the whole window rather than this page, and read brand_totals.brand_source before quoting either: 'configured_patterns' means this domain's own brand patterns (Settings > Search Console > Brand Patterns, the same classifier the ctr_curve report segments with); 'domain_name_fallback' means NOTHING is configured and the split was derived from the domain's own name as a substring - a heuristic that finds the obvious brand term and nothing else (no misspellings, no product names, no abbreviations), so it under-counts branded traffic and is not the customer's own definition. When brand_totals is null nothing could be classified at all: is_brand is then false on every row and brand='brand' returns nothing, which means 'nothing to classify against', NOT 'no branded traffic' - that state cannot answer a branded-versus-non-branded question and must not be reported as zero.","title":"Brand"},"description":"Only 'brand' or 'nonbrand' queries. Every row also carries is_brand regardless of this filter. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs - page on with next_offset, not by adding len(items) to your own offset. For 'how much of my traffic is branded' read brand_totals, which covers the whole window rather than this page, and read brand_totals.brand_source before quoting either: 'configured_patterns' means this domain's own brand patterns (Settings > Search Console > Brand Patterns, the same classifier the ctr_curve report segments with); 'domain_name_fallback' means NOTHING is configured and the split was derived from the domain's own name as a substring - a heuristic that finds the obvious brand term and nothing else (no misspellings, no product names, no abbreviations), so it under-counts branded traffic and is not the customer's own definition. When brand_totals is null nothing could be classified at all: is_brand is then false on every row and brand='brand' returns nothing, which means 'nothing to classify against', NOT 'no branded traffic' - that state cannot answer a branded-versus-non-branded question and must not be reported as zero."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(clicks|impressions|ctr|position|position_worst|clicks_change_pct|impressions_change_pct|ctr_change_pct|position_change_pct)$","description":"Server-side ordering. 'clicks' (default), 'impressions', 'ctr', 'position' (best-first) and 'position_worst' (worst-first) all rank across the WHOLE property (ranking_scope='property' in the response). 'ctr' with neither an explicit min_impressions nor an exact-match 'query' filter applies a default impression floor first - see min_impressions - so a query with a single impression cannot top the ranking; the floor actually used comes back as min_impressions_applied. The four *_change_pct values turn compare_previous on by themselves and, unlike the five above, rank ONLY within the already-fetched page (ranking_scope='fetched_page' - see declining_only and that field's own description): clicks_change_pct/impressions_change_pct/ctr_change_pct sort most-declined-first, position_change_pct sorts most-worsened-first. A row with no previous-window match always sorts last on a *_change_pct key, and raising 'limit' widens the candidate set that kind of sort runs over. Because that candidate set is always selected by CURRENT clicks, a query that fell to zero clicks this window can never surface on a *_change_pct sort at any limit, even if it is the property's single biggest decliner - inherent to the design, not a bug.","default":"clicks","title":"Sort By"},"description":"Server-side ordering. 'clicks' (default), 'impressions', 'ctr', 'position' (best-first) and 'position_worst' (worst-first) all rank across the WHOLE property (ranking_scope='property' in the response). 'ctr' with neither an explicit min_impressions nor an exact-match 'query' filter applies a default impression floor first - see min_impressions - so a query with a single impression cannot top the ranking; the floor actually used comes back as min_impressions_applied. The four *_change_pct values turn compare_previous on by themselves and, unlike the five above, rank ONLY within the already-fetched page (ranking_scope='fetched_page' - see declining_only and that field's own description): clicks_change_pct/impressions_change_pct/ctr_change_pct sort most-declined-first, position_change_pct sorts most-worsened-first. A row with no previous-window match always sorts last on a *_change_pct key, and raising 'limit' widens the candidate set that kind of sort runs over. Because that candidate set is always selected by CURRENT clicks, a query that fell to zero clicks this window can never surface on a *_change_pct sort at any limit, even if it is the property's single biggest decliner - inherent to the design, not a bug."},{"name":"declining_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only rows whose clicks fell vs the immediately preceding window (clicks_change_pct < 0). Turns compare_previous on by itself, so you do not need to pass both. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs, since the true count of declining rows beyond this page is unknown - see has_more. The candidate set itself is always selected by CURRENT clicks, so a row that fell all the way to zero clicks this window was never fetched to begin with and can never appear here, even though it lost 100% of its traffic.","default":false,"title":"Declining Only"},"description":"Only rows whose clicks fell vs the immediately preceding window (clicks_change_pct < 0). Turns compare_previous on by itself, so you do not need to pass both. Filters the already-fetched page, so it may return fewer than 'limit' rows; total becomes null once this runs, since the true count of declining rows beyond this page is unknown - see has_more. The candidate set itself is always selected by CURRENT clicks, so a row that fell all the way to zero clicks this window was never fetched to begin with and can never appear here, even though it lost 100% of its traffic."},{"name":"min_impressions","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Only queries with at least this many impressions. When omitted and sort_by=ctr with no exact-match 'query' filter, defaults to 50 - a CTR computed from a single impression is a coincidence, not a ranking. Pass 1 to see every query regardless of impressions. The floor actually applied is echoed back as min_impressions_applied.","title":"Min Impressions"},"description":"Only queries with at least this many impressions. When omitted and sort_by=ctr with no exact-match 'query' filter, defaults to 50 - a CTR computed from a single impression is a coincidence, not a ranking. Pass 1 to see every query regardless of impressions. The floor actually applied is echoed back as min_impressions_applied."},{"name":"position_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":1},{"type":"null"}],"description":"Only queries whose average position is at least this.","title":"Position Min"},"description":"Only queries whose average position is at least this."},{"name":"position_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":1},{"type":"null"}],"description":"Only queries whose average position is at most this (10 = page one).","title":"Position Max"},"description":"Only queries whose average position is at most this (10 = page one)."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return each query's clicks/impressions/ctr/position for the immediately preceding window of equal length, plus the percentage change (prev_* and *_change_pct fields). Costs a second internal call, ranked by clicks, so coverage is best for the busiest queries - a low-traffic query may show a null comparison even if it had data last period. Position is inverted - LOWER is better - so a NEGATIVE position_change_pct is an improvement, not a regression.","default":false,"title":"Compare Previous"},"description":"Also return each query's clicks/impressions/ctr/position for the immediately preceding window of equal length, plus the percentage change (prev_* and *_change_pct fields). Costs a second internal call, ranked by clicks, so coverage is best for the busiest queries - a low-traffic query may show a null comparison even if it had data last period. Position is inverted - LOWER is better - so a NEGATIVE position_change_pct is an improvement, not a regression."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":1000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCQueriesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/_registered":{"get":{"tags":["Search Console"],"summary":"List the GSC reports available to run","description":"Every registered report_key with its execution tier and freshness window in seconds.","operationId":"list_registered_report_keys_api_public_v1_domains__domain_id__gsc_reports__registered_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GSCRegisteredReport_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/_summary":{"get":{"tags":["Search Console"],"summary":"Get the GSC reports hub summary","description":"The latest completed run per registered report (status, row count, a per-report preview snippet) in 'items', plus 28-day KPIs. kpis is null when the property has no data in the trailing 28 days. A report with has_run=false has never been run - POST its report_key to /reports/{report_key}/run to start one.","operationId":"get_reports_summary_api_public_v1_domains__domain_id__gsc_reports__summary_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCHubSummaryPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/runs/{run_id}":{"get":{"tags":["Search Console"],"summary":"Get a GSC report run","description":"Poll this after POSTing .../reports/{report_key}/run. status moves pending -> running -> completed|failed; result is null until completed.","operationId":"get_report_run_api_public_v1_domains__domain_id__gsc_reports_runs__run_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"summary_only","in":"query","required":false,"schema":{"type":"boolean","description":"Skip hydrating the full row payload from ClickHouse; return only the summary.","default":false,"title":"Summary Only"},"description":"Skip hydrating the full row payload from ClickHouse; return only the summary."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCReportRunResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/runs/{run_id}/rows":{"get":{"tags":["Search Console"],"summary":"Get a paginated slice of a completed GSC report run's rows","description":"Server-side pagination over a completed run's result.rows, in 'items'. Optional case-insensitive 'search'. Offset-paginated, not cursor-paginated, so 'next_cursor' is always null; page further with 'offset'.","operationId":"get_report_run_rows_api_public_v1_domains__domain_id__gsc_reports_runs__run_id__rows_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":25,"title":"Limit"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Search"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCReportRunRowsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/{report_key}/latest":{"get":{"tags":["Search Console"],"summary":"Get the latest completed run for a GSC report","description":"Null if this report has never completed a run for this domain.","operationId":"get_latest_report_run_api_public_v1_domains__domain_id__gsc_reports__report_key__latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"report_key","in":"path","required":true,"schema":{"type":"string","title":"Report Key"}},{"name":"summary_only","in":"query","required":false,"schema":{"type":"boolean","description":"Skip hydrating the full row payload from ClickHouse; return only the summary.","default":false,"title":"Summary Only"},"description":"Skip hydrating the full row payload from ClickHouse; return only the summary."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GSCReportRunResponse"},{"type":"null"}],"title":"Response Get Latest Report Run Api Public V1 Domains  Domain Id  Gsc Reports  Report Key  Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/reports/{report_key}/run":{"post":{"tags":["Search Console"],"summary":"Run a GSC report","description":"Starts a report compute, or returns the already-queued/fresh run for the same params (see each report's freshness window via GET .../reports/_registered). Poll poll_url for the result. Requires an Idempotency-Key header so a retry does not start a second compute.","operationId":"run_report_api_public_v1_domains__domain_id__gsc_reports__report_key__run_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"report_key","in":"path","required":true,"schema":{"type":"string","title":"Report Key"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GSCReportRunRequest"},{"type":"null"}],"title":"Payload"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/search-appearance":{"get":{"tags":["Search Console"],"summary":"Get GSC performance broken down by search appearance type","description":"Clicks/impressions/CTR/position per search-appearance type (e.g. rich results, AMP) for the web search type only (default: 30 days).","operationId":"get_search_appearance_api_public_v1_domains__domain_id__gsc_search_appearance_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 30 days back.","title":"Start Date"},"description":"YYYY-MM-DD, default 30 days back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD, default 2 days ago.","title":"End Date"},"description":"YYYY-MM-DD, default 2 days ago."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GSCSearchAppearancePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/gsc/url-patterns":{"get":{"tags":["Search Console"],"summary":"List URL-template pattern overrides for this domain","description":"Manual regex overrides for the page-template benchmark report; take priority over auto-detected templates.","operationId":"list_url_patterns_api_public_v1_domains__domain_id__gsc_url_patterns_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_GSCURLPattern_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/health":{"get":{"tags":["Domain health"],"summary":"Cross-module health snapshot for a domain","description":"The fastest way to answer 'how is this site doing'. One call returns every module configured for the domain - uptime, RUM, CrUX, synthetic, indexing, robots.txt, web analytics, Search Console, Google Analytics - each compared against the prior period. The window is fixed at the last 28 complete UTC days vs. the 28 days before that (meta.range_start/range_end and compare_range_start/compare_range_end name the exact dates); it is not configurable here. Three states are distinguishable, never collapsed: a block is null when that module is not in configured_modules; an empty-but-present block (e.g. crux: {mobile: null, desktop: null}) when it is configured but has nothing for this window yet; and populated when it does. ga also carries account_token_valid - a dead Google OAuth token freezes that module's figures rather than zeroing them, which otherwise reads identically to a quiet month.\n\nThis fixed trailing window can disagree with a calendar-month comparison - e.g. 'has uptime got worse than last month' - since 'the 28 days before the last 28 days' and 'last calendar month' are usually different date ranges. For that specific question use GET /uptime/availability?compare_previous=true&compare_mode=calendar_month, which names both windows explicitly.\n\n'freshness=cached' (default) may return a snapshot up to one hour old - fine for a dashboard, wrong for 'is the site up right now' during an incident. Check 'meta.cache_age_seconds' before trusting 'uptime.is_up', or pass 'freshness=live' to force a fresh computation (slower, cache_age_seconds always 0).","operationId":"get_domain_health_api_public_v1_domains__domain_id__health_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"freshness","in":"query","required":false,"schema":{"enum":["cached","live"],"type":"string","description":"'cached': may be up to 1 hour stale, see meta.cache_age_seconds. 'live': bypasses the cache and recomputes every module now.","default":"cached","title":"Freshness"},"description":"'cached': may be up to 1 hour stale, see meta.cache_age_seconds. 'live': bypasses the cache and recomputes every module now."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainHealthSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/diagnostics":{"get":{"tags":["Indexing"],"summary":"Get indexing collection diagnostics","description":"Why inspections might be running slower than expected: sitemap URL count, total/unique inspection counts, the last 30 days of daily inspection volume, and expected-vs-actual coverage given the enrolled properties' daily budget.","operationId":"get_indexing_diagnostics_api_public_v1_domains__domain_id__indexing_diagnostics_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexingDiagnosticsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/groups":{"get":{"tags":["Indexing"],"summary":"List URL groups","description":"User-defined URL-space slices for this domain, ordered by sort_order then name. Every group is returned in one response - not paginated, so 'has_more' is always false.","operationId":"list_url_groups_api_public_v1_domains__domain_id__indexing_groups_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_URLGroupPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Indexing"],"summary":"Create a URL group","description":"Returns a conflict problem if path_prefix already exists for this domain.","operationId":"create_url_group_api_public_v1_domains__domain_id__indexing_groups_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/URLGroupCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/URLGroupPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/groups/{group_id}":{"delete":{"tags":["Indexing"],"summary":"Delete a URL group","description":"sitemap_discovered_urls.group_id reverts to NULL via FK for URLs in this group.","operationId":"delete_url_group_api_public_v1_domains__domain_id__indexing_groups__group_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Group Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Indexing"],"summary":"Update a URL group","operationId":"update_url_group_api_public_v1_domains__domain_id__indexing_groups__group_id__patch","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/URLGroupUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/URLGroupPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/inspections":{"get":{"tags":["Indexing"],"summary":"List URL Inspection results","description":"Most recent GSC URL Inspection result per URL, newest-inspected first. Cursor-paginated \u2013 pass 'cursor' from the previous page's 'next_cursor'. 'status' filters on coverage_state (repeatable); 'kind' narrows to one cross-cutting issue category (canonical_conflict, rich_fail, stale_30/60/90, noindex, page_with_redirect, crawled_not_indexed, discovered_not_indexed). stale_30/60/90 bucket URLs still marked 'Submitted and indexed' by days since last_crawl_time: stale_30 is [30, 60) days, stale_60 is [60, 90) days, stale_90 is 90+ days. The three bands are mutually exclusive - no URL falls in more than one - so calling this endpoint once per band and summing the 'total' from each call gives an honest 'stale 30+ days overall' count. The last two kinds (crawled_not_indexed, discovered_not_indexed) are the largest not-indexed buckets on most domains and are the way to LIST those URLs - GET /indexing/issue-summary counts them but carries only three example URLs each.","operationId":"list_inspections_api_public_v1_domains__domain_id__indexing_inspections_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":500,"default":"","title":"Search"}},{"name":"status","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"maxItems":30,"title":"Status"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string","pattern":"^(|canonical_conflict|rich_fail|stale_30|stale_60|stale_90|noindex|page_with_redirect|crawled_not_indexed|discovered_not_indexed)$","default":"","title":"Kind"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_InspectionEntry_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/issue-summary":{"get":{"tags":["Indexing"],"summary":"Get aggregate indexing issue counts","description":"Bucketed counts of the current (latest-inspection-per-URL) issue types, each with up to 3 example URLs. Stale-crawl bands are mutually exclusive and only apply to URLs Google still lists as indexed. The 'coverage' block's 'inspected_urls' is usually a fraction of GET .../indexing/sitemaps' 'total_submitted' and is not a random sample of it - see CoverageSummary.","operationId":"get_issue_summary_api_public_v1_domains__domain_id__indexing_issue_summary_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueSummaryPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/priority-urls":{"get":{"tags":["Indexing"],"summary":"List priority-queue URLs","description":"URLs flagged for inspection ahead of the regular sitemap rotation, newest first. The whole queue is returned in one response - not paginated, so 'has_more' is always false.","operationId":"list_priority_urls_api_public_v1_domains__domain_id__indexing_priority_urls_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_PriorityURLPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Indexing"],"summary":"Add a URL to the priority inspection queue","description":"Capped at 200 URLs per domain. The URL's host must match the domain or one of its subdomains, otherwise GSC inspections for it always fail.","operationId":"add_priority_url_api_public_v1_domains__domain_id__indexing_priority_urls_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriorityURLCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriorityURLPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/priority-urls/{priority_url_id}":{"delete":{"tags":["Indexing"],"summary":"Remove a URL from the priority queue","operationId":"delete_priority_url_api_public_v1_domains__domain_id__indexing_priority_urls__priority_url_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"priority_url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Priority Url Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/properties":{"get":{"tags":["Indexing"],"summary":"List enrolled GSC properties","description":"GSC properties contributing daily URL-inspection budget to this domain. Every enrolled property is returned in 'items' in one response - not paginated, so 'has_more' is always false.","operationId":"list_indexing_properties_api_public_v1_domains__domain_id__indexing_properties_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexingPropertiesListPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/properties/{property_id}":{"delete":{"tags":["Indexing"],"summary":"Remove a property from this domain's indexing capacity","description":"Does not touch the GSC module's primary linkage. If the property is fully orphaned afterward (no primary linkage, no other enrollment), its ClickHouse inspection/sitemap history is purged in the background.","operationId":"unenroll_indexing_property_api_public_v1_domains__domain_id__indexing_properties__property_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"property_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Property Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Indexing"],"summary":"Enroll a GSC property in this domain's indexing capacity","description":"Idempotent - re-enrolling an already-enrolled property is a no-op. Enforces the workspace's plan limit on indexing properties and, on plans without subdirectory support, rejects non-root URL_PREFIX properties. On a fresh enrollment, best-effort triggers an immediate inspection via the indexing-collector; failure to trigger is non-fatal, the property is picked up on the next 24h schedule regardless.","operationId":"enroll_indexing_property_api_public_v1_domains__domain_id__indexing_properties__property_id__post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"property_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Property Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexingPropertyEnrollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/sitemaps":{"get":{"tags":["Indexing"],"summary":"Get GSC sitemap coverage stats","description":"Latest submitted/indexed/error/warning counts per sitemap, aggregated across every GSC property enrolled in this domain's indexing capacity. Reflects the last time Search Console reported sitemap stats, not live crawl data. Cursor-paginated: a domain can have hundreds of enrolled sitemaps.\n\n'indexed_urls', 'total_indexed' and 'index_coverage_pct' are all null, not 0, when Search Console reported no indexed count for any sitemap on the property. Google does not always populate that figure - it is absent for large sitemaps and for properties it has not computed - and rendering that absence as 0 says 'none of your pages are indexed', which is a different and usually false claim. 'indexed_reporting' gives the machine-readable reason for the null: 'not_reported_by_google' means Google never computed it (not evidence of nothing indexed), 'no_linked_property' means no GSC property is enrolled at all, 'no_sitemaps' means a property is enrolled but has no sitemap on record - an earlier problem than Google simply not having computed a count yet. When null, read indexed page counts from the URL inspection data (GET /indexing/issue-summary, the 'coverage' block) instead, which is derived from per-URL verdicts rather than from Google's sitemap report.\n\n'data_through' is the most recent date Search Console reported stats for any enrolled sitemap. GET .../sitemap/stats reports its own 'data_through' from the sitemap scanner, a different, independently-scheduled process - compare the two before treating a gap between total_submitted here and total_urls_discovered there as a discrepancy rather than one of them being stale.","operationId":"get_sitemap_stats_api_public_v1_domains__domain_id__indexing_sitemaps_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":100,"title":"Limit"},"description":"Page size."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__schemas__public__indexing__SitemapStatsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/state-changes":{"get":{"tags":["Indexing"],"summary":"Get recent per-URL coverage-state transitions","description":"Current vs prior inspection for every URL whose latest inspection landed in the trailing 'days' days, up to 'limit' rows. Use this to derive 'what changed' \u2013 coverage-state flips, rich-results verdict flips, robots.txt access flips, canonical re-picks, and first-time discoveries. Not cursor-paginated - 'next_cursor' is always null; if 'has_more' comes back true, request a larger 'limit' for the rest.","operationId":"get_state_changes_api_public_v1_domains__domain_id__indexing_state_changes_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"default":7,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_StateChangeEntry_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/timeline":{"get":{"tags":["Indexing"],"summary":"Get indexing coverage-state timeline","description":"Daily URL count per coverage_state over the trailing 'days' days, with each day carrying forward the latest known state per URL as of that date.","operationId":"get_indexing_timeline_api_public_v1_domains__domain_id__indexing_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexingTimelinePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/indexing/url-history":{"get":{"tags":["Indexing"],"summary":"Get every past inspection for one URL","description":"Full inspection history for a single URL across enrolled properties, newest first, up to 'limit' rows. Not cursor-paginated - 'next_cursor' is always null; if 'has_more' comes back true, request a larger 'limit' for the rest.","operationId":"get_url_history_api_public_v1_domains__domain_id__indexing_url_history_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":2048,"title":"Url"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_InspectionEntry_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/limits":{"get":{"tags":["Workspaces and domains"],"summary":"Get plan limits and usage","description":"Plan allowances and current usage for the workspace this domain belongs to, as the dashboard's plan-usage panel reports them. Read it before adding CrUX or Synthetic URLs and before choosing a check interval: these are the numbers that gate those writes, so anything that does not fit here will be refused there.\n\nEvery count is workspace-wide, not per-domain - see the field descriptions.","operationId":"get_domain_limits_api_public_v1_domains__domain_id__limits_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainLimitsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/overview":{"get":{"tags":["Domain overview"],"summary":"Cross-module summary for a domain, with period comparison","description":"The one call that answers 'how is this domain doing?'. Returns the same nine module blocks the dashboard's domain overview page shows - RUM, CrUX, synthetic, uptime, robots.txt, indexing, Search Console, Google Analytics and web analytics - over a fixed window of the last 28 days, which is not configurable.\n\n'comparison' carries the previous-period value and the movement for the headline numbers that have one: CrUX, Search Console, Google Analytics and web analytics. The other five modules report no historical figure at all, so they are absent from 'comparison' rather than given an invented baseline - see the schema for what each would require instead.\n\nA null block means that module has no data for this domain. If its query failed, the module is named in 'unavailable_modules' instead, and the rest of the response is still valid - one broken module does not fail the call.\n\nBlocks are served from each module's own cache and can be up to an hour old (robots.txt, five minutes). For a freshness-controlled answer, and for 'is the site up right now', use GET /domains/{domain_id}/health.","operationId":"get_domain_overview_api_public_v1_domains__domain_id__overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainOverviewPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/data/{template_id}":{"post":{"tags":["Reports"],"summary":"Collect report data as JSON (read-only)","description":"Runs the same data collection a PDF render uses and returns it as JSON; no PDF is produced and nothing is written. Despite the POST verb this is a read and requires only 'read:reports', not 'run:reports' - the body carries date_range/compare_previous overrides, it does not trigger a job. Cached up to 8 hours per (template, date_range, compare_previous, sections_config).","operationId":"get_report_data_api_public_v1_domains__domain_id__reports_data__template_id__post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportGenerateRequestPublic","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDataResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/jobs/{job_id}":{"get":{"tags":["Reports"],"summary":"Check a PDF report job","description":"Status of a job started by POST .../reports/{template_id}/generate. 'queued' or 'running' means keep polling; 'completed' means download_url is set and the PDF is ready; 'failed' means error is set and the job will not retry itself. The result - and this status - expires 1 hour after the job finishes successfully, or 24 hours after it fails.","operationId":"get_report_job_api_public_v1_domains__domain_id__reports_jobs__job_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/jobs/{job_id}/download":{"get":{"tags":["Reports"],"summary":"Download a completed PDF report","description":"Streams the rendered PDF for a job whose status is 'completed'. Returns 409 if the job has not finished yet - poll GET .../reports/jobs/{job_id} first. The PDF expires 1 hour after the job completes, same as the job status.","operationId":"download_report_job_api_public_v1_domains__domain_id__reports_jobs__job_id__download_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/templates":{"get":{"tags":["Reports"],"summary":"List report templates for a domain","operationId":"list_templates_api_public_v1_domains__domain_id__reports_templates_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ReportTemplatePublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Reports"],"summary":"Create a report template","description":"Domain-scoped shared config; capped at 100 per domain.","operationId":"create_template_api_public_v1_domains__domain_id__reports_templates_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplateCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplatePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/templates/{template_id}":{"delete":{"tags":["Reports"],"summary":"Delete a report template","operationId":"delete_template_api_public_v1_domains__domain_id__reports_templates__template_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Reports"],"summary":"Get a report template","operationId":"get_template_api_public_v1_domains__domain_id__reports_templates__template_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplatePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Reports"],"summary":"Update a report template","description":"Every field is optional; an omitted field leaves the stored value unchanged.","operationId":"update_template_api_public_v1_domains__domain_id__reports_templates__template_id__put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplateUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplatePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/templates/{template_id}/duplicate":{"post":{"tags":["Reports"],"summary":"Duplicate a report template","description":"Copies sections_config under a new name ('<name> (Copy)', de-duplicated).","operationId":"duplicate_template_api_public_v1_domains__domain_id__reports_templates__template_id__duplicate_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportTemplatePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/reports/{template_id}/generate":{"post":{"tags":["Reports"],"summary":"Generate a PDF report (async)","description":"Queues a PDF render and returns immediately with a job id instead of the PDF: the internal render runs through WeasyPrint after collecting data from every configured module, which can take up to several minutes and must not hold an API worker for that long. Poll GET .../reports/jobs/{job_id} for status, then GET .../reports/jobs/{job_id}/download once status is 'completed'. Requires an Idempotency-Key header so a retry replays the same job instead of starting a second render. Limited to 5 renders per minute per credential, separately from the general run limit.","operationId":"generate_report_api_public_v1_domains__domain_id__reports__template_id__generate_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"template_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Template Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportGenerateRequestPublic","default":{}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/ai-bots":{"get":{"tags":["Robots.txt"],"summary":"Get AI-bot governance view","description":"Per-AI-bot allow/disallow state under the domain's current robots.txt, enriched with vendor/purpose metadata, plus static governance presets (block-training, block-all, allow-retrieval-only) a caller can paste into a robots.txt directly - this API never writes robots.txt content itself. Narrow with 'purpose' and/or 'blocked_only'; 'declared_but_empty' and 'presets' always describe the whole robots.txt and are never narrowed by them.","operationId":"ai_bots_api_public_v1_domains__domain_id__robots_ai_bots_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"purpose","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(training|retrieval|preview|agent)$"},{"type":"null"}],"description":"Return only bots with this purpose.","title":"Purpose"},"description":"Return only bots with this purpose."},{"name":"blocked_only","in":"query","required":false,"schema":{"type":"boolean","description":"Return only the bots robots.txt currently disallows.","default":false,"title":"Blocked Only"},"description":"Return only the bots robots.txt currently disallows."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIBotGovernancePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/annotations":{"get":{"tags":["Robots.txt"],"summary":"List robots.txt change annotations","description":"User notes attached to specific robots.txt change records, newest change first. Every annotation is returned in one response - not paginated, so 'has_more' is always false.","operationId":"list_annotations_api_public_v1_domains__domain_id__robots_annotations_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_AnnotationPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Robots.txt"],"summary":"Add a note to a robots.txt change","description":"'change_at' and 'new_version_id' (if set) must match an existing row in the changes timeline.","operationId":"create_annotation_api_public_v1_domains__domain_id__robots_annotations_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/annotations/{annotation_id}":{"delete":{"tags":["Robots.txt"],"summary":"Delete a robots.txt change annotation","operationId":"delete_annotation_api_public_v1_domains__domain_id__robots_annotations__annotation_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Annotation Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Robots.txt"],"summary":"Update a robots.txt change annotation","operationId":"update_annotation_api_public_v1_domains__domain_id__robots_annotations__annotation_id__patch","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Annotation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/changes":{"get":{"tags":["Robots.txt"],"summary":"List robots.txt changes","description":"Every detected content change in the window (default trailing 90 days), newest first. Cursor-paginated. Pass include_diff=true to get the actual changed lines: the line counts say how much changed but never what, so without it the only answer to 'what changed in robots.txt' is '2 lines added, 1 removed'. A content_change row can still come back with an empty diff and content_unchanged: true - the two stored versions are textually identical, which is the row's own label being wrong about there being an edit, not a computation gap. An empty result is NOT 'robots.txt has never changed': the window is what it is, so window_start/window_end state what was searched, and latest_change_at reports whether any change exists outside it.","operationId":"get_robots_txt_changes_api_public_v1_domains__domain_id__robots_changes_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"include_diff","in":"query","required":false,"schema":{"type":"boolean","description":"Include a unified diff of the changed lines on each row. Costs one extra query and is truncated on very large files.","default":false,"title":"Include Diff"},"description":"Include a unified diff of the changed lines on each row. Costs one extra query and is truncated on very large files."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RobotsTxtChangesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/crawler-access":{"get":{"tags":["Robots.txt"],"summary":"Get the per-crawler allow/disallow matrix","description":"Whether each known search-engine and AI crawler may fetch 'path' (default '/') under the domain's current robots.txt, computed by the same matcher the on-demand inspector uses. One item per crawler category ('search_engine', 'ai_crawler', 'other') in 'items' - not paginated, so 'has_more' is always false.","operationId":"get_crawler_access_api_public_v1_domains__domain_id__robots_crawler_access_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"path","in":"query","required":false,"schema":{"type":"string","description":"Path to evaluate access for.","default":"/","title":"Path"},"description":"Path to evaluate access for."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrawlerAccessPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/diff-impacts":{"get":{"tags":["Robots.txt"],"summary":"Get per-change before/after impact for every change in the window","description":"Keyed by changed_at (ISO 8601). Returns every precomputed impact in the window in one call rather than paginating - robots.txt changes are comparatively rare events (bounded by 'days', max ~3 years), so batching avoids one request per row without risking an unbounded payload the way a raw event table would.","operationId":"get_diff_impacts_batch_api_public_v1_domains__domain_id__robots_diff_impacts_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":1095,"minimum":1,"description":"Lookback window.","default":365,"title":"Days"},"description":"Lookback window."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ChangeImpactPublic"},"title":"Response Get Diff Impacts Batch Api Public V1 Domains  Domain Id  Robots Diff Impacts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/health":{"get":{"tags":["Robots.txt"],"summary":"Get robots.txt fetch health","description":"Latest fetch metadata (status, timing, cache headers) plus the trailing 7-day fetch/failure counts.","operationId":"get_health_api_public_v1_domains__domain_id__robots_health_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RobotsTxtHealthPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/latest":{"get":{"tags":["Robots.txt"],"summary":"Get the latest robots.txt content","description":"The current robots.txt body for this domain, with validation findings. Note which timestamp answers which question: fetched_at is when this content last CHANGED (a version row is only written on a change), last_verified_at is the most recent successful fetch, and last_checked is when the findings were computed. On a file that has not changed in months, fetched_at is months old while the file is still being re-fetched many times a day.","operationId":"get_latest_version_api_public_v1_domains__domain_id__robots_latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RobotsTxtVersionPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/overlapping-rules":{"get":{"tags":["Robots.txt"],"summary":"Detect overlapping rules in the stored robots.txt","description":"Duplicate, redundant, or contradictory Allow/Disallow pairs in the currently stored file, in 'items'. Not cursor-paginated, so 'next_cursor' is always absent - but 'has_more' is true when a user-agent group was too large to analyse, and 'truncated_groups' names those groups. An empty 'items' with 'has_more' true means the file was not fully checked, not that it is clean.","operationId":"overlapping_rules_for_stored_api_public_v1_domains__domain_id__robots_overlapping_rules_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverlappingRulesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/overlapping-rules/query":{"post":{"tags":["Robots.txt"],"summary":"Detect overlapping rules in a candidate robots.txt (read-only)","description":"Read-only despite being a POST - lints 'content' without storing it. Body-only because candidate content does not fit in query parameters. Omit 'content' to lint the currently stored file instead (equivalent to the GET variant). Pairs are returned in 'items'; 'has_more' is true and 'truncated_groups' is populated when a user-agent group was too large to analyse.","operationId":"overlapping_rules_with_content_api_public_v1_domains__domain_id__robots_overlapping_rules_query_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverlappingRulesQueryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverlappingRulesPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/simulate/query":{"post":{"tags":["Robots.txt"],"summary":"Simulate a candidate robots.txt before publishing it (read-only)","description":"Read-only despite being a POST - it never stores 'candidate_content', it only evaluates it. Body-only because candidate file content does not fit in query parameters. Returns per-crawler allow/disallow flips relative to the currently stored file so a caller can review the blast radius before publishing.","operationId":"simulate_api_public_v1_domains__domain_id__robots_simulate_query_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulateResponsePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/sitemap-conflicts":{"get":{"tags":["Robots.txt"],"summary":"List sitemap URLs blocked by robots.txt","description":"Sitemap URLs the current robots.txt disallows, from the most recent conflict check. Cursor-paginated by URL and anchored to that check's timestamp, so paging through a large result stays consistent even if a newer check completes midway.","operationId":"get_sitemap_conflicts_api_public_v1_domains__domain_id__robots_sitemap_conflicts_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SitemapConflictPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/sitemap-conflicts/recompute":{"post":{"tags":["Robots.txt"],"summary":"Recompute the sitemap-vs-robots.txt conflict check","description":"Re-diffs the sitemap URLs already stored for this domain against the current robots.txt rules and overwrites the stored conflict result (GET .../sitemap-conflicts/summary) - it does NOT fetch a fresh sitemap. That means it only clears the half of 'stale' caused by the check lagging behind an already-stored sitemap (current_sitemap_url_count differing from total_sitemap_urls). If 'stale' is true because the underlying scan itself is old (see scan_age_days on the summary), this call is a no-op for that cause - last_checked_at will move to a new timestamp but 'stale' will stay true, because the sitemap it checked against has not changed. Only a fresh sitemap scan clears that, and starting one is a dashboard-only action today (the domain's settings page 'Scan Now' button) - GET .../sitemap/stats reports can_initiate_scan and scan_age_days so a caller can tell whether one is overdue, but there is no way to trigger it from this API. The underlying recompute work streams up to ~1M sitemap URLs against the current robots.txt rules with blocking ClickHouse writes, so it is dispatched to a background worker and this call returns 202 immediately - poll GET .../sitemap-conflicts/summary afterward. It fully recomputes and overwrites the stored result keyed by a fresh checked_at, so a retried or duplicate call cannot corrupt or duplicate state. Requires an Idempotency-Key header so a retry does not queue a second recompute.","operationId":"recompute_sitemap_conflicts_api_public_v1_domains__domain_id__robots_sitemap_conflicts_recompute_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/sitemap-conflicts/summary":{"get":{"tags":["Robots.txt"],"summary":"Get the sitemap-vs-robots.txt conflict summary","description":"Totals from the most recent conflict check: how many sitemap URLs existed and how many were blocked by robots.txt at the moment it ran. The check is meant to recompute automatically whenever robots.txt or the sitemap changes, but that has not always happened reliably, so this also reports current_sitemap_url_count (the sitemap as currently stored) and 'stale', which covers two independent failure modes: the stored sitemap moving since the check ran (current_sitemap_url_count != total_sitemap_urls), or the underlying sitemap scan itself going old (scan_age_days/scan_data_through) - a check that exactly matches a four-month-old scan is not current data even though the counts agree. Read 'stale' before quoting blocked_urls_count. POST .../sitemap-conflicts/recompute only fixes the first cause: it re-diffs the already-stored sitemap against the current robots.txt and never fetches a new sitemap, so it cannot clear staleness caused by an old scan - see GET .../sitemap/stats for that (a dashboard-only 'Scan Now' action today, not one this API can trigger).","operationId":"get_sitemap_conflicts_summary_api_public_v1_domains__domain_id__robots_sitemap_conflicts_summary_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitemapConflictsSummaryPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/test-url":{"get":{"tags":["Robots.txt"],"summary":"Test whether a URL is allowed for one crawler","description":"Evaluates 'url' against the domain's current stored robots.txt for 'user_agent', which defaults to 'Googlebot' rather than '*' - on a site with a Googlebot-specific group the two give different verdicts, so the response echoes back which agent was evaluated. To test a candidate file that is not yet stored, use 'POST .../test-urls/query' with a one-element 'urls' list and 'content' set.","operationId":"test_url_api_public_v1_domains__domain_id__robots_test_url_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":2048,"title":"Url"}},{"name":"user_agent","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"User Agent"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestURLResultPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/test-urls/query":{"post":{"tags":["Robots.txt"],"summary":"Test a batch of URLs against robots.txt (read-only)","description":"Read-only despite being a POST - it evaluates up to 500 URLs and never stores anything. Body-only because the URL list does not fit in query parameters. Pass 'content' to test a candidate file that is not yet stored; omit it to test against the current one. One result per tested URL in 'items' - not paginated, so 'has_more' is always false.","operationId":"test_urls_api_public_v1_domains__domain_id__robots_test_urls_query_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestURLBatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_TestURLResultPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/timeline":{"get":{"tags":["Robots.txt"],"summary":"List fetched robots.txt versions","description":"Every fetched robots.txt version in the window, newest first. Defaults to the trailing 90 days; 'start'/'end' together can span at most 36 months back from 'end'. Cursor-paginated.","operationId":"get_robots_txt_timeline_api_public_v1_domains__domain_id__robots_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_RobotsTxtVersionPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/top-blocked":{"get":{"tags":["Robots.txt"],"summary":"Get high-traffic URLs blocked by robots.txt","description":"Cross-references the robots.txt matcher against the domain's top Search Console clicks and top web-analytics pageviews over the trailing 28 days, for Googlebot only (the one crawler whose behaviour is observable via GSC). Returns only the blocked subset of a bounded top-10000 candidate set in 'items', so this is not a paginated table read - 'has_more' is always false and 'total' is the blocked count.","operationId":"top_blocked_api_public_v1_domains__domain_id__robots_top_blocked_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopBlockedPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/robots/validation/history":{"get":{"tags":["Robots.txt"],"summary":"List robots.txt validation results","description":"One row per validation run (RFC 9309 + Google-specific checks) in the window, newest first.","operationId":"get_validation_history_api_public_v1_domains__domain_id__robots_validation_history_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_RobotsTxtValidationPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/engagement":{"get":{"tags":["RUM"],"summary":"User engagement metrics","description":"Interaction counts (clicks, scrolls, ...) and scroll-depth milestones for the window. Aggregate only. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_engagement_api_public_v1_domains__domain_id__rum_engagement_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":90,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now. Max 90.","title":"Period Days"},"description":"Window length in days, ending now. Max 90."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngagementPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/engagement/correlation":{"get":{"tags":["RUM"],"summary":"Performance vs. engagement correlation histogram","description":"Bins sessions by a performance metric (LCP/CLS/INP/FCP/TTFB) and overlays an engagement signal (scroll depth, clicks, bounce rate, ...) per bin, to show whether slower experiences correlate with less engagement. Aggregate only.","operationId":"get_engagement_correlation_api_public_v1_domains__domain_id__rum_engagement_correlation_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD).","title":"Start Date"},"description":"Start date (YYYY-MM-DD)."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD).","title":"End Date"},"description":"End date (YYYY-MM-DD)."},{"name":"performance_metric","in":"query","required":false,"schema":{"type":"string","pattern":"^(LCP|CLS|INP|FCP|TTFB)$","default":"LCP","title":"Performance Metric"}},{"name":"engagement_metric","in":"query","required":false,"schema":{"type":"string","pattern":"^(scroll_25|scroll_50|scroll_75|scroll_90|scroll_100|click|bounce_rate|rage_click|form_abandonment|time_on_page|attention_score|time_to_first_interaction)$","default":"scroll_50","title":"Engagement Metric"}},{"name":"device_type","in":"query","required":false,"schema":{"type":"string","description":"Device type filter.","default":"Mobile","title":"Device Type"},"description":"Device type filter."},{"name":"filters","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"JSON-encoded array of {dimension, type, value} objects. dimension is one of url|country|browser; type is one of contains|notContains|equals|notEquals|regex|notRegex|batch|notBatch.","title":"Filters"},"description":"JSON-encoded array of {dimension, type, value} objects. dimension is one of url|country|browser; type is one of contains|notContains|equals|notEquals|regex|notRegex|batch|notBatch."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngagementCorrelationPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/errors":{"get":{"tags":["RUM"],"summary":"Error counts by type","description":"Error occurrence and affected-session counts, grouped by error type, device type and browser. `total` is the number of rows in that breakdown, not the number of errors - the two differ by orders of magnitude. Aggregate only: no free text, no session_id, no scope required beyond 'read:rum'.","operationId":"get_errors_api_public_v1_domains__domain_id__rum_errors_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorSummaryPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/errors/details":{"get":{"tags":["RUM"],"summary":"Detailed error records with stack traces","description":"Individual error occurrences with stack trace, filename, and page context, cursor-paginated. session_id is omitted; url, referrer, and resource_url have their query string stripped; message, stack, filename, element_id, and element_class have embedded email addresses and long opaque tokens replaced with '[redacted]'. All are attacker-controlled free text harvested from the monitored site, and all of this applies unconditionally.","operationId":"get_error_details_api_public_v1_domains__domain_id__rum_errors_details_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":30,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in error messages, types, and URLs.","title":"Search"},"description":"Search in error messages, types, and URLs."},{"name":"error_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by error type.","title":"Error Type"},"description":"Filter by error type."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by error kind (the message up to the first colon).","title":"Kind"},"description":"Filter by error kind (the message up to the first colon)."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser.","title":"Browser"},"description":"Filter by browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by operating system.","title":"Os"},"description":"Filter by operating system."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetailsPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/errors/groups":{"get":{"tags":["RUM"],"summary":"Distinct error kinds","description":"Errors grouped by kind (the message up to the first colon), so repeated instances of one underlying error collapse into one row. sample_url always has its query string stripped. Defaults to the trailing 30 days, the same as every other RUM read here - it used to default to 7, which on a domain whose ingestion had stopped landed entirely inside the gap and reported no errors at all while GET /rum/overview reported 227,791 for its own window. An empty result still means only 'none in THIS window': check window_start/window_end against the data_through on any RUM overview call before reporting a site as error-free.","operationId":"get_error_groups_api_public_v1_domains__domain_id__rum_errors_groups_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":30,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in error messages, types, and URLs.","title":"Search"},"description":"Search in error messages, types, and URLs."},{"name":"error_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by error type.","title":"Error Type"},"description":"Filter by error type."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser.","title":"Browser"},"description":"Filter by browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by operating system.","title":"Os"},"description":"Filter by operating system."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorGroupPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/events":{"get":{"tags":["RUM"],"summary":"Individual RUM events","description":"Raw event stream (vitals, errors, interactions, navigations) for debugging, cursor-paginated. Limited to a 7-day window. session_id is always omitted and url always has its query string stripped - there is no scope that widens this.","operationId":"get_events_api_public_v1_domains__domain_id__rum_events_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":7,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now. Max 7.","title":"Period Days"},"description":"Window length in days, ending now. Max 7."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"event_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by event type.","title":"Event Type"},"description":"Filter by event type."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventDetailPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/geographic":{"get":{"tags":["RUM"],"summary":"Geographic distribution of RUM metrics","description":"Session counts and Core Web Vitals by country, with a combined good/needs-improvement/poor classification. Aggregate only. sort_by and min_sessions are evaluated over every country the domain has data for (country is a closed ~250-value dimension, so there is no busiest-N candidate set to reason about); min_sessions_applied in the response states the floor actually used, so 'which country has the worst LCP' carries its own caveat instead of depending on a floor the caller has to guess. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_geographic_api_public_v1_domains__domain_id__rum_geographic_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"cls_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(atf|btf)$"},{"type":"null"}],"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS).","default":"atf","title":"Cls Mode"},"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS)."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(sessions|lcp_p75|cls_p75|inp_p75|fcp_p75|ttfb_p75)$","description":"Server-side ordering across every country in the domain. 'sessions' (default) ranks by traffic; the metric keys rank worst-first on that metric's 75th percentile, so sort_by=lcp_p75 answers 'which country has the worst LCP' with nothing excluded ahead of time. Countries with no sample for the chosen metric sort last.","default":"sessions","title":"Sort By"},"description":"Server-side ordering across every country in the domain. 'sessions' (default) ranks by traffic; the metric keys rank worst-first on that metric's 75th percentile, so sort_by=lcp_p75 answers 'which country has the worst LCP' with nothing excluded ahead of time. Countries with no sample for the chosen metric sort last."},{"name":"min_sessions","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Drop countries with fewer than this many sessions before sort_by is applied. Below a few dozen sessions a 75th-percentile value is mostly noise - a single-session country (35,748ms LCP on one visit) topped a worst-LCP ranking before this default existed - so the default is 20, matching the min_sessions floor used elsewhere in the codebase for the same reason (app/services/gsc_reports/reports/post_click_engagement.py). Pass 1 to see every country with any data at all, including single-visit ones. The floor actually applied is echoed back as min_sessions_applied.","default":20,"title":"Min Sessions"},"description":"Drop countries with fewer than this many sessions before sort_by is applied. Below a few dozen sessions a 75th-percentile value is mostly noise - a single-session country (35,748ms LCP on one visit) topped a worst-LCP ranking before this default existed - so the default is 20, matching the min_sessions floor used elsewhere in the codebase for the same reason (app/services/gsc_reports/reports/post_click_engagement.py). Pass 1 to see every country with any data at all, including single-visit ones. The floor actually applied is echoed back as min_sessions_applied."},{"name":"url_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by URL.","title":"Url Filter"},"description":"Filter by URL."},{"name":"url_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch","title":"Url Filter Type"},"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch"},{"name":"country_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter country code.","title":"Country Filter"},"description":"Filter by 2-letter country code."},{"name":"country_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Country Filter Type"},"description":"Same filter types as url_filter_type."},{"name":"browser_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser name.","title":"Browser Filter"},"description":"Filter by browser name."},{"name":"browser_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Browser Filter Type"},"description":"Same filter types as url_filter_type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeographicPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/has-data":{"get":{"tags":["RUM"],"summary":"Whether this domain has any RUM data at all","description":"Cheap existence probe, independent of any date range: has this domain ever received a single RUM vital event? Use this to distinguish 'no data in the selected window' from 'tracking script never installed'.","operationId":"get_has_data_api_public_v1_domains__domain_id__rum_has_data_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HasDataPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/overview":{"get":{"tags":["RUM"],"summary":"RUM overview for a domain","description":"Aggregate Core Web Vitals, event/session/error counts, and device/browser breakdowns for the window. All percentiles (50th/75th/90th/95th) are always returned. Values are aggregated across sessions - no session_id appears anywhere in this response, regardless of scope. device_breakdown and browser_breakdown are event COUNTS, not vitals - this endpoint has no per-device Core Web Vitals split; pass device_type to get one device's own vitals (call it twice, once per device, to compare Mobile against Desktop). The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_overview_api_public_v1_domains__domain_id__rum_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":540,"minimum":1,"description":"Window length in days, ending now. Max 540 (18 months).","default":30,"title":"Days"},"description":"Window length in days, ending now. Max 540 (18 months)."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start (pairs with end_date; overrides days).","title":"Start Date"},"description":"Explicit window start (pairs with end_date; overrides days)."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"cls_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(atf|btf)$"},{"type":"null"}],"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS).","default":"atf","title":"Cls Mode"},"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS)."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return each Core Web Vital's 75th-percentile value for the immediately preceding window of the same length, plus the percentage change - so 'has TTFB improved since last month' is one call instead of two calls and a manual subtraction. Costs a second query. The previous window is computed from the same days/start_date/end_date and reuses every other filter unchanged, including device_type, so pass device_type to compare one device's trend.","default":false,"title":"Compare Previous"},"description":"Also return each Core Web Vital's 75th-percentile value for the immediately preceding window of the same length, plus the percentage change - so 'has TTFB improved since last month' is one call instead of two calls and a manual subtraction. Costs a second query. The previous window is computed from the same days/start_date/end_date and reuses every other filter unchanged, including device_type, so pass device_type to compare one device's trend."},{"name":"url_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by URL.","title":"Url Filter"},"description":"Filter by URL."},{"name":"url_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch","title":"Url Filter Type"},"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch"},{"name":"country_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter country code.","title":"Country Filter"},"description":"Filter by 2-letter country code."},{"name":"country_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Country Filter Type"},"description":"Same filter types as url_filter_type."},{"name":"browser_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser name.","title":"Browser Filter"},"description":"Filter by browser name."},{"name":"browser_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Browser Filter Type"},"description":"Same filter types as url_filter_type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RUMOverviewPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/pages":{"get":{"tags":["RUM"],"summary":"Page performance breakdown","description":"Core Web Vitals aggregated per URL, cursor-paginated. Timing fields are in milliseconds except CLS, which is unitless. failed_only restricts to pages where LCP, CLS, or INP misses the 'good' threshold (2500ms, 0.1, 200ms); cls_status/lcp_status restrict to one specific band and can be combined - cls_status=poor with lcp_status=good answers 'pages where CLS is bad but LCP is fine' in one call, evaluated over every page in the domain. No session_id - this is a URL-level aggregate, not a per-visitor row. URLs are returned as stored and may retain a query string. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_pages_api_public_v1_domains__domain_id__rum_pages_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"failed_only","in":"query","required":false,"schema":{"type":"boolean","description":"Only pages that fail Core Web Vitals - LCP, CLS or INP measured over its 'good' threshold, OR with no sample for that metric at all. Unmeasured pages are deliberately kept (a page nobody has interacted with has no INP, and that is worth seeing), but they are counted separately in failure_rollup: *_failing_pages is measured-and-over-threshold only, *_no_sample_pages is the rest. Read those two, not one labelled number, before quoting a figure.","default":false,"title":"Failed Only"},"description":"Only pages that fail Core Web Vitals - LCP, CLS or INP measured over its 'good' threshold, OR with no sample for that metric at all. Unmeasured pages are deliberately kept (a page nobody has interacted with has no INP, and that is worth seeing), but they are counted separately in failure_rollup: *_failing_pages is measured-and-over-threshold only, *_no_sample_pages is the rest. Read those two, not one labelled number, before quoting a figure."},{"name":"cls_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(good|needs_improvement|poor)$"},{"type":"null"}],"description":"Restrict to pages whose 75th-percentile CLS falls in this Core Web Vitals band: good (<= 0.1), needs_improvement (<= 0.25), or poor (> 0.25). Runs as a HAVING clause over every page in the domain - total reflects the true whole-domain count, not a pre-fetched slice. Pages with no CLS sample never match.","title":"Cls Status"},"description":"Restrict to pages whose 75th-percentile CLS falls in this Core Web Vitals band: good (<= 0.1), needs_improvement (<= 0.25), or poor (> 0.25). Runs as a HAVING clause over every page in the domain - total reflects the true whole-domain count, not a pre-fetched slice. Pages with no CLS sample never match."},{"name":"lcp_status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(good|needs_improvement|poor)$"},{"type":"null"}],"description":"Restrict to pages whose 75th-percentile LCP falls in this Core Web Vitals band: good (<= 2500ms), needs_improvement (<= 4000ms), or poor (> 4000ms). Combine with cls_status - both conditions must match, evaluated together over the whole domain.","title":"Lcp Status"},"description":"Restrict to pages whose 75th-percentile LCP falls in this Core Web Vitals band: good (<= 2500ms), needs_improvement (<= 4000ms), or poor (> 4000ms). Combine with cls_status - both conditions must match, evaluated together over the whole domain."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return each page's 75th-percentile LCP/INP/CLS for the immediately preceding window of the same length, plus the change. Costs a second query. NOTE: comparing requires both windows to cover the same pages, so this mode ranks within a candidate set of at most the 500 busiest pages rather than across every page in storage. The set's actual size comes back as candidate_set_size, with 'notice' naming it against the domain-wide total, and pagination stops at the end of it: rows outside the candidate set are not reachable in this mode at any cursor. Without compare_previous, sort_by ranks server-side over the whole domain and every row is pageable.","default":false,"title":"Compare Previous"},"description":"Also return each page's 75th-percentile LCP/INP/CLS for the immediately preceding window of the same length, plus the change. Costs a second query. NOTE: comparing requires both windows to cover the same pages, so this mode ranks within a candidate set of at most the 500 busiest pages rather than across every page in storage. The set's actual size comes back as candidate_set_size, with 'notice' naming it against the domain-wide total, and pagination stops at the end of it: rows outside the candidate set are not reachable in this mode at any cursor. Without compare_previous, sort_by ranks server-side over the whole domain and every row is pageable."},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(pageviews|visitors|lcp_p75|cls_p75|inp_p75|fcp_p75|ttfb_p75|lcp_p75_regression|cls_p75_regression|inp_p75_regression)$","description":"Server-side ordering. 'pageviews' (default) and 'visitors' rank by traffic; the metric keys rank worst-first on that metric's 75th percentile, so the first page of results is the worst page overall, not merely the worst of the busiest. Pages with no sample for the chosen metric sort last. A percentile sort_by (any key other than pageviews/visitors) applies a default min_pageviews floor of 20 unless the request sets min_pageviews explicitly or passes url_filter - see min_pageviews. The '*_regression' keys rank by how much that metric got WORSE against the previous window, as an ABSOLUTE change (p75_*_ms this window minus p75_*_ms the previous one - CLS in its unitless score), NOT by the percentage shown in the paired p75_*_change_pct column: percentage also depends on the baseline, so the two orderings can disagree (a page down from 50ms to 200ms shows a large percentage on a small absolute change). Compute p75_*_ms minus prev_p75_*_ms per row if you need to see the sort key itself. They require compare_previous=true and therefore rank within a bounded candidate set of the busiest pages: the response reports its size as candidate_set_size and states it against the domain-wide total in 'notice'. Read those two before presenting a result as the domain's worst regressions - on a real domain the set was 109 pages against 27,689 with traffic.","default":"pageviews","title":"Sort By"},"description":"Server-side ordering. 'pageviews' (default) and 'visitors' rank by traffic; the metric keys rank worst-first on that metric's 75th percentile, so the first page of results is the worst page overall, not merely the worst of the busiest. Pages with no sample for the chosen metric sort last. A percentile sort_by (any key other than pageviews/visitors) applies a default min_pageviews floor of 20 unless the request sets min_pageviews explicitly or passes url_filter - see min_pageviews. The '*_regression' keys rank by how much that metric got WORSE against the previous window, as an ABSOLUTE change (p75_*_ms this window minus p75_*_ms the previous one - CLS in its unitless score), NOT by the percentage shown in the paired p75_*_change_pct column: percentage also depends on the baseline, so the two orderings can disagree (a page down from 50ms to 200ms shows a large percentage on a small absolute change). Compute p75_*_ms minus prev_p75_*_ms per row if you need to see the sort key itself. They require compare_previous=true and therefore rank within a bounded candidate set of the busiest pages: the response reports its size as candidate_set_size and states it against the domain-wide total in 'notice'. Read those two before presenting a result as the domain's worst regressions - on a real domain the set was 109 pages against 27,689 with traffic."},{"name":"min_pageviews","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Only pages with at least this many measured pageviews. When omitted and sort_by ranks by a percentile metric (lcp_p75, cls_p75, inp_p75, fcp_p75, ttfb_p75, or a *_regression key) with no url_filter, defaults to 20 - a percentile from a single visit is a measurement artefact, not a worst-page finding. sort_by=pageviews/visitors and any url_filter lookup get no implicit floor. Pass 1 to see every page regardless of traffic. The floor actually applied is echoed back as min_pageviews_applied.","title":"Min Pageviews"},"description":"Only pages with at least this many measured pageviews. When omitted and sort_by ranks by a percentile metric (lcp_p75, cls_p75, inp_p75, fcp_p75, ttfb_p75, or a *_regression key) with no url_filter, defaults to 20 - a percentile from a single visit is a measurement artefact, not a worst-page finding. sort_by=pageviews/visitors and any url_filter lookup get no implicit floor. Pass 1 to see every page regardless of traffic. The floor actually applied is echoed back as min_pageviews_applied."},{"name":"cls_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(atf|btf)$"},{"type":"null"}],"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS).","default":"atf","title":"Cls Mode"},"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS)."},{"name":"url_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by URL.","title":"Url Filter"},"description":"Filter by URL."},{"name":"url_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch","title":"Url Filter Type"},"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch"},{"name":"country_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter country code.","title":"Country Filter"},"description":"Filter by 2-letter country code."},{"name":"country_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Country Filter Type"},"description":"Same filter types as url_filter_type."},{"name":"browser_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser name.","title":"Browser Filter"},"description":"Filter by browser name."},{"name":"browser_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Browser Filter Type"},"description":"Same filter types as url_filter_type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePerformancePageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/pageviews":{"get":{"tags":["RUM"],"summary":"Aggregated page views","description":"Page views aggregated by session and URL, with parsed Core Web Vitals attached in 'data'. Cursor-paginated. 'total' and 'has_more' reflect a hard 50000-raw-event cap on the underlying query - once a window's matching events exceed that cap, 'total' is omitted (it is no longer a real count) and 'has_more' stays true even past the last page this endpoint can serve; narrow the date range or filters to get a reliable total. session_id is always omitted and url always has its query string stripped - there is no scope that widens this.","operationId":"get_pageviews_api_public_v1_domains__domain_id__rum_pageviews_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start. Defaults to 30 days before end_date.","title":"Start Date"},"description":"Explicit window start. Defaults to 30 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end. Defaults to now.","title":"End Date"},"description":"Explicit window end. Defaults to now."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor.","title":"Cursor"},"description":"Opaque pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in URLs.","title":"Search"},"description":"Search in URLs."},{"name":"browser","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser.","title":"Browser"},"description":"Filter by browser."},{"name":"os","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by operating system.","title":"Os"},"description":"Filter by operating system."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"has_lcp","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Only page views with LCP data.","title":"Has Lcp"},"description":"Only page views with LCP data."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventDetailPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals":{"get":{"tags":["RUM"],"summary":"Core Web Vitals, by device type","description":"Full metric distribution (average, 50th/75th/95th/99th percentile, min, max) for each Core Web Vital, grouped by device type. Milliseconds for LCP/INP/FCP/TTFB; CLS is a unitless score. Defaults to a trailing 30-day window ending now, same as GET .../rum/overview - to compare a value from the two endpoints, pass the same explicit start_date/end_date to both and check data_through on each: when collection lags or has stopped, the default window silently shrinks to whatever days actually hold data, and two calls with slightly different windows can disagree by multiples over the tail alone. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_vitals_api_public_v1_domains__domain_id__rum_vitals_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VitalsPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/distribution-timeline":{"get":{"tags":["RUM"],"summary":"Good/needs-improvement/poor split over time","description":"Percentage of sessions in each Core Web Vitals status bucket (good/needs-improvement/poor), per time bucket. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_vitals_distribution_timeline_api_public_v1_domains__domain_id__rum_vitals_distribution_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"granularity","in":"query","required":false,"schema":{"type":"string","pattern":"^(day|week)$","description":"Bucket size.","default":"day","title":"Granularity"},"description":"Bucket size."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"cls_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(atf|btf)$"},{"type":"null"}],"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS).","default":"atf","title":"Cls Mode"},"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS)."},{"name":"url_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by URL.","title":"Url Filter"},"description":"Filter by URL."},{"name":"url_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch","title":"Url Filter Type"},"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch"},{"name":"country_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter country code.","title":"Country Filter"},"description":"Filter by 2-letter country code."},{"name":"country_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Country Filter Type"},"description":"Same filter types as url_filter_type."},{"name":"browser_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser name.","title":"Browser Filter"},"description":"Filter by browser name."},{"name":"browser_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Browser Filter Type"},"description":"Same filter types as url_filter_type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DistributionTimelinePublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/inp/script-domains":{"get":{"tags":["RUM"],"summary":"Top INP script sources","description":"The script URLs most often responsible for slow interactions (INP), ranked by occurrence, with 75th-percentile INP in milliseconds. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_inp_script_domains_api_public_v1_domains__domain_id__rum_vitals_inp_script_domains_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of top script domains to return.","default":20,"title":"Limit"},"description":"Number of top script domains to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/INPScriptDomainsPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/inp/subparts/timeline":{"get":{"tags":["RUM"],"summary":"INP timing breakdown, as a daily time series","description":"Daily average INP sub-phase durations in milliseconds: input delay, processing time, presentation delay. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_inp_subparts_timeline_api_public_v1_domains__domain_id__rum_vitals_inp_subparts_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/INPSubpartsTimelinePageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/lcp/element-types":{"get":{"tags":["RUM"],"summary":"LCP breakdown by element type","description":"Largest Contentful Paint grouped by the element type that was LCP (img, text, ...), with a 75th-percentile LCP in milliseconds per type. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_lcp_element_types_api_public_v1_domains__domain_id__rum_vitals_lcp_element_types_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LCPElementTypesPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/lcp/element-types/timeline":{"get":{"tags":["RUM"],"summary":"LCP element type breakdown, as a daily time series","description":"Daily counts and 75th-percentile LCP (milliseconds) per element type. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_lcp_element_types_timeline_api_public_v1_domains__domain_id__rum_vitals_lcp_element_types_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LCPElementTypesTimelinePageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/lcp/resources":{"get":{"tags":["RUM"],"summary":"Top LCP resources","description":"The resource URLs (images, background images) most often responsible for the LCP element, ranked by occurrence, with 75th-percentile LCP in milliseconds. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_lcp_resources_api_public_v1_domains__domain_id__rum_vitals_lcp_resources_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Number of top resources to return.","default":20,"title":"Limit"},"description":"Number of top resources to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LCPResourcesPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/lcp/subparts":{"get":{"tags":["RUM"],"summary":"LCP timing breakdown","description":"Average LCP sub-phase durations in milliseconds: time to first byte, resource load delay, resource load time, and element render delay. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_lcp_subparts_api_public_v1_domains__domain_id__rum_vitals_lcp_subparts_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LCPSubpartsPublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/lcp/subparts/timeline":{"get":{"tags":["RUM"],"summary":"LCP timing breakdown, as a daily time series","description":"Daily average LCP sub-phase durations in milliseconds. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_lcp_subparts_timeline_api_public_v1_domains__domain_id__rum_vitals_lcp_subparts_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LCPSubpartsTimelinePageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/timeline":{"get":{"tags":["RUM"],"summary":"Core Web Vitals over time","description":"Time-bucketed Core Web Vitals, one metric at a time. Milliseconds for LCP/INP/FCP/TTFB; CLS is a unitless score. Defaults to LCP: pass metric to plot a different one. Without a single metric to plot, a trend mixes LCP, INP, CLS, FCP, TTFB and several non-Core-Web-Vital navigation-timing points (DNS, RESPONSE, WINDOW_LOAD, ...) into one series, which is not a trend a customer can read - the default removes that mix rather than exposing it. data_through is the newest date the returned points actually reach - when collection lags or has stopped, the series simply stops short of end_date, and an empty tail looks identical to a genuinely quiet one without checking it. Pass period='last_month' for the last COMPLETE calendar month instead of naming the boundaries yourself; window_start/window_end always report what it resolved to. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_vitals_timeline_api_public_v1_domains__domain_id__rum_vitals_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":540,"minimum":1},{"type":"null"}],"description":"Window length in days, ending now.","title":"Period Days"},"description":"Window length in days, ending now."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window start.","title":"Start Date"},"description":"Explicit window start."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit window end.","title":"End Date"},"description":"Explicit window end."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"granularity","in":"query","required":false,"schema":{"type":"string","pattern":"^(hour|day|week)$","description":"Bucket size.","default":"day","title":"Granularity"},"description":"Bucket size."},{"name":"metric","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"One metric to plot (LCP, INP, CLS, FCP, TTFB, or a navigation-timing point such as DNS, RESPONSE, WINDOW_LOAD). Defaults to LCP.","default":"LCP","title":"Metric"},"description":"One metric to plot (LCP, INP, CLS, FCP, TTFB, or a navigation-timing point such as DNS, RESPONSE, WINDOW_LOAD). Defaults to LCP."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."},{"name":"cls_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(atf|btf)$"},{"type":"null"}],"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS).","default":"atf","title":"Cls Mode"},"description":"'atf' (Above the Fold, first CLS measurement per session - default) or 'btf' (Below the Fold, final cumulative CLS)."},{"name":"url_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by URL.","title":"Url Filter"},"description":"Filter by URL."},{"name":"url_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch","title":"Url Filter Type"},"description":"contains (default) | notContains | equals | notEquals | startsWith | notStartsWith | endsWith | notEndsWith | regex | notRegex | batch | notBatch"},{"name":"country_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by 2-letter country code.","title":"Country Filter"},"description":"Filter by 2-letter country code."},{"name":"country_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Country Filter Type"},"description":"Same filter types as url_filter_type."},{"name":"browser_filter","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by browser name.","title":"Browser Filter"},"description":"Filter by browser name."},{"name":"browser_filter_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Same filter types as url_filter_type.","title":"Browser Filter Type"},"description":"Same filter types as url_filter_type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VitalTimelinePublicResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/rum/vitals/ttfb/subparts/timeline":{"get":{"tags":["RUM"],"summary":"TTFB timing breakdown, as a daily time series","description":"Daily average TTFB sub-phase durations in milliseconds: DNS, connection, TLS, request, response. The window ends where end_date says: the public API cancels the whole-day padding the internal handlers apply for the dashboard, so end_date=2026-07-31 no longer counts 2026-08-01 events. That is a deliberate, authorised breaking change against numbers published before this release. window_end always states the exact window queried.","operationId":"get_ttfb_subparts_timeline_api_public_v1_domains__domain_id__rum_vitals_ttfb_subparts_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Start of the window.","title":"Start Date"},"description":"Start of the window."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"End of the window.","title":"End Date"},"description":"End of the window."},{"name":"device_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device type ('Mobile' or 'Desktop').","title":"Device Type"},"description":"Filter by device type ('Mobile' or 'Desktop')."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTFBSubpartsTimelinePageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/sitemap/config":{"get":{"tags":["Sitemap"],"summary":"Get sitemap scan configuration","operationId":"get_sitemap_config_api_public_v1_domains__domain_id__sitemap_config_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitemapScanConfigPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/sitemap/rate-limit":{"get":{"tags":["Sitemap"],"summary":"Check whether a sitemap scan can be started","description":"Only reflects whether a scan is currently in progress - there is no cooldown window.","operationId":"check_rate_limit_api_public_v1_domains__domain_id__sitemap_rate_limit_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitemapRateLimitPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/sitemap/stats":{"get":{"tags":["Sitemap"],"summary":"Get sitemap scan statistics","description":"Total discovered URLs and the outcome of the most recent scan. 'data_through' and 'scan_age_days' make that scan's age explicit - named to match GET .../indexing/sitemaps's own 'data_through' so the two can be compared directly. The two totals routinely disagree because the sitemap scanner and Search Console's own reporting run on independent schedules, not because either is wrong. A large sitemap can go a long time between scans; no route on this API or MCP can force one, so 'data_through' and 'scan_age_days' are the only way to see how old the current data is.","operationId":"get_scan_stats_api_public_v1_domains__domain_id__sitemap_stats_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__schemas__public__sitemap__SitemapStatsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/sitemap/urls":{"get":{"tags":["Sitemap"],"summary":"Export every discovered sitemap URL","description":"The full discovered-URL list for this domain, cursor-paginated with no ceiling on total pages retrieved - keep passing 'next_cursor' as 'cursor' until 'has_more' is false. Ordered by url ascending, which matches the (domain_id, url) index, so each page is an efficient indexed seek rather than a table scan. This is the supported way to compute sitemap-vs-robots.txt conflicts yourself; the equivalent server-side trigger is not exposed (see robots.py). Pass 'q' to confirm a path was actually discovered before spending a call testing it against robots.txt - a rule-match answer on a guessed path that was never in the sitemap answers a question nobody asked. Backed by a domain-scoped trigram index (migration 096), not a table scan.","operationId":"list_discovered_urls_api_public_v1_domains__domain_id__sitemap_urls_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(success|failed)$"},{"type":"null"}],"description":"Filter to one scan outcome.","title":"Status"},"description":"Filter to one scan outcome."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":3,"maxLength":500},{"type":"null"}],"description":"Case-insensitive substring match against 'url' - a plain substring, not a glob or regex. Minimum 3 characters; anything shorter is rejected, since it would match most of a large sitemap and answer nothing. Use this to check whether a path exists before testing it against robots.txt.","title":"Q"},"description":"Case-insensitive substring match against 'url' - a plain substring, not a glob or regex. Minimum 3 characters; anything shorter is rejected, since it would match most of a large sitemap and answer nothing. Use this to check whether a path exists before testing it against robots.txt."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":1000,"minimum":1},{"type":"null"}],"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SitemapDiscoveredUrlPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl":{"get":{"tags":["SSL"],"summary":"SSL certificate chain detail","description":"Full certificate chain from the most recent scheduled check: subject, issuer, validity window, OCSP/revocation status, HSTS, and expiry. When no chain scan has run for this domain yet, returns 200 with scan_status 'never_scanned' - the same condition GET .../ssl/summary reports as status 'unknown'. days_until_expiry is still populated then if the uptime checker has separately observed this certificate's expiry (source 'uptime_checker'); every other field stays null, because a day count is not a chain scan. The same fallback also applies when scan_status is 'scanned' but the scan itself could not determine an expiry date - every other field is then real scan data, and only days_until_expiry (and source) come from the uptime checker. A 404 here means the domain does not exist, not that it has not been scanned.","operationId":"get_chain_api_public_v1_domains__domain_id__ssl_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSLChainPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl/domain-expiration":{"get":{"tags":["SSL"],"summary":"Domain registration expiration","description":"WHOIS-derived registration expiry for the domain itself - distinct from certificate expiry. 404 if no check has run yet; check_supported is false for TLDs whose registry does not expose WHOIS expiration data.","operationId":"get_domain_expiration_api_public_v1_domains__domain_id__ssl_domain_expiration_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainExpirationPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl/events":{"get":{"tags":["SSL"],"summary":"SSL certificate change events","description":"The certificate history diffed into discrete events - issued, renewed, issuer_changed, sans_changed - newest first, in 'items'. Looks back 365 days, capped at 50 events; a certificate that renews a handful of times a year will show every renewal within that window. Not paginated, so 'has_more' is always false.","operationId":"get_events_api_public_v1_domains__domain_id__ssl_events_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSLEventsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl/history":{"get":{"tags":["SSL"],"summary":"SSL certificate history","description":"One row per observed certificate check in the window, newest first, in 'items'. Defaults to the last 90 days, which is also the hard cap. Not paginated - capped at 10000 rows server-side, so 'has_more' is always false.","operationId":"get_history_api_public_v1_domains__domain_id__ssl_history_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SSLCertificatePublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl/latest":{"get":{"tags":["SSL"],"summary":"Latest SSL certificate","description":"The most recently observed certificate. 404 if none has been recorded yet.","operationId":"get_latest_api_public_v1_domains__domain_id__ssl_latest_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSLCertificatePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/ssl/summary":{"get":{"tags":["SSL"],"summary":"SSL status summary","description":"Quick-glance status ('healthy'|'warning'|'critical'|'unknown') derived from the chain detail, with a human-readable reason and the full issue/warning list. When status is 'unknown' because no chain scan has run, days_until_expiry is still populated if the uptime checker has separately observed this certificate's expiry (source 'uptime_checker') - status stays 'unknown' regardless, since a day count is not a chain health verdict. The same fallback also fires whenever a completed scan could not determine an expiry date of its own, whatever status that scan reports - status, chain_valid, has_hsts and ocsp_status all stay exactly what the scan found; only days_until_expiry (and source) come from the uptime checker. A scan is never reported 'healthy' while its own expiry date is unknown - see days_until_expiry.","operationId":"get_summary_api_public_v1_domains__domain_id__ssl_summary_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSLSummaryPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/compare":{"get":{"tags":["Synthetic"],"summary":"Compare synthetic performance between two periods","description":"Average Lighthouse performance score across two windows, for one strategy, plus the percent change. Each window is named EITHER by a preset (period1='last_week', period2='last_7_days' - weeks are Monday to Sunday, 'last_month' is the last COMPLETE calendar month) OR by its own explicit start/end pair; passing both for the same window is a 400 rather than a silent precedence rule, and naming only one date of a pair is a 400 too - an explicit window needs both ends. Leave a window entirely unnamed - no preset, no dates - and it defaults instead: period1 defaults to 'last_week', period2 to the complete calendar week immediately before that, so a bare call with no period arguments at all compares the last two complete weeks - both windows always full, never a week still in progress. The two windows are resolved independently, so a preset on one, dates on the other, and a default on either is any combination you like. Whichever way they were resolved, all four dates come back in the response - that is what a caller quotes, and it is the only way to know which calendar days a preset or a default became. The change and the trend word are always read chronologically - from whichever window starts earlier to whichever starts later - so the answer does not depend on which range you pass as period1. Scores are Lighthouse's 0-1 fractions; multiply by 100 for the score out of 100. period1_test_count/period2_test_count report how many runs actually landed in each window; when either is 0, that window's score, the percent change, and the trend all come back null rather than a fabricated zero and a fabricated -100 per cent.","operationId":"compare_synthetic_periods_api_public_v1_domains__domain_id__synthetic_compare_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"period1_start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit start of period1, paired with period1_end. Alternative to the period1 preset; giving one without the other is a 400. Omit period1 and both of these to default period1 to 'last_week'.","title":"Period1 Start"},"description":"Explicit start of period1, paired with period1_end. Alternative to the period1 preset; giving one without the other is a 400. Omit period1 and both of these to default period1 to 'last_week'."},{"name":"period1_end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit end of period1, paired with period1_start. See period1_start.","title":"Period1 End"},"description":"Explicit end of period1, paired with period1_start. See period1_start."},{"name":"period2_start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit start of period2, paired with period2_end. Alternative to the period2 preset; giving one without the other is a 400. Omit period2 and both of these to default period2 to the complete calendar week immediately before period1's own default.","title":"Period2 Start"},"description":"Explicit start of period2, paired with period2_end. Alternative to the period2 preset; giving one without the other is a 400. Omit period2 and both of these to default period2 to the complete calendar week immediately before period1's own default."},{"name":"period2_end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Explicit end of period2, paired with period2_start. See period2_start.","title":"Period2 End"},"description":"Explicit end of period2, paired with period2_start. See period2_start."},{"name":"period1","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"Names the FIRST window. A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date. Omit this and period1_start/period1_end to default this window to 'last_week'.","title":"Period1"},"description":"Names the FIRST window. A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date. Omit this and period1_start/period1_end to default this window to 'last_week'."},{"name":"period2","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"Names the SECOND window. A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date. Omit this and period2_start/period2_end to default this window to the complete calendar week immediately before period1's own default.","title":"Period2"},"description":"Names the SECOND window. A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date. Omit this and period2_start/period2_end to default this window to the complete calendar week immediately before period1's own default."},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","pattern":"^(mobile|desktop)$","default":"mobile","title":"Strategy"}},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(google_psi|pod)$"},{"type":"null"}],"description":"Restrict both windows to one testing location. Omit to blend every one.","title":"Location Type"},"description":"Restrict both windows to one testing location. Omit to blend every one."},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to one pod. Implies location_type=pod; may be passed alone.","title":"Pod Id"},"description":"Filter to one pod. Implies location_type=pod; may be passed alone."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticCompareResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/location-limits":{"get":{"tags":["Synthetic"],"summary":"Get the synthetic location limits for this workspace","operationId":"get_synthetic_location_limits_api_public_v1_domains__domain_id__synthetic_location_limits_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticLocationLimits"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/locations":{"get":{"tags":["Synthetic"],"summary":"List available synthetic testing locations","description":"Every location tests can run from: Google's Servers plus every enabled pod, in 'items'. Not scoped to this domain's own configuration - see urls/{url_id}/locations for that. Not paginated, so 'has_more' is always false.","operationId":"list_available_synthetic_locations_api_public_v1_domains__domain_id__synthetic_locations_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SyntheticAvailableLocation_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/locations-with-data":{"get":{"tags":["Synthetic"],"summary":"List locations with collected test data","description":"Locations that have at least one test result for this domain (or one URL, if url_id is passed), with a test count per location, in 'items'. Not paginated, so 'has_more' is always false.","operationId":"list_synthetic_locations_with_data_api_public_v1_domains__domain_id__synthetic_locations_with_data_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SyntheticDomainLocationWithData_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/opportunities":{"get":{"tags":["Synthetic"],"summary":"Aggregate Lighthouse opportunities across recent runs","description":"The Lighthouse improvement opportunities that recur across a domain's recent runs, in 'items', ordered by typical saving. This is the endpoint for 'what are the biggest opportunities to speed up the site'. GET /synthetic/tests/{test_id} answers only 'what did this one run say', which is a different answer: two runs of the same domain hours apart surfaced completely disjoint lists (one led with unused JavaScript at 1650ms, the other with redirects at 133ms), so a single run picked at random misrepresents the site. Each entry carries runs_seen, so a cost present in every run is distinguishable from one run's blip, and a median saving rather than a mean so one pathological run does not set the figure. Mobile and desktop are never blended - pass strategy. Reads at most 20 runs, newest first. The envelope states exactly what was covered: runs_found, runs_read, runs_without_data, and the window_start/window_end the runs span. A run whose stored audit rows have aged out contributes nothing and is counted in runs_without_data - it is NOT a run with no opportunities, and coverage_notice says so whenever that happened. Lighthouse's savings are per-run estimates and are not additive: do not sum them into a site total. The full Lighthouse description text is not repeated per entry; fetch one run's detail for that.","operationId":"get_synthetic_opportunities_api_public_v1_domains__domain_id__synthetic_opportunities_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","pattern":"^(mobile|desktop)$","description":"Mobile is the stricter profile and the one Google ranks on.","default":"mobile","title":"Strategy"},"description":"Mobile is the stricter profile and the one Google ranks on."},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(google_psi|pod)$"},{"type":"null"}],"title":"Location Type"}},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to one pod. Implies location_type=pod; may be passed alone.","title":"Pod Id"},"description":"Filter to one pod. Implies location_type=pod; may be passed alone."},{"name":"runs","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"How many of the most recent runs to aggregate over, 1-20. More runs make runs_seen a stronger signal; fewer keep the window closer to today.","default":10,"title":"Runs"},"description":"How many of the most recent runs to aggregate over, 1-20. More runs make runs_seen a stronger signal; fewer keep the window closer to today."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticOpportunitiesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/overview":{"get":{"tags":["Synthetic"],"summary":"Get synthetic monitoring overview","description":"Lighthouse performance, accessibility, best-practices and SEO scores, plus Core Web Vitals (TTFB, LCP, CLS), for mobile and desktop: each Lighthouse score is the average and each Core Web Vital is the 75th percentile, both over the trailing 28 complete days. Pass url_id to scope to one monitored URL; omit for the whole domain. Trend arrows compare against the preceding 28-day window. Comparing two location_type calls (e.g. google_psi vs pod) as a head-to-head requires both to be current - a location that has silently stopped reporting still returns a 28-day average, just one anchored weeks earlier. Check latest_test_age_days and is_stale on each call first; an unfiltered call blending both locations reports the gap itself in stale_locations_notice. Two dates come back and they mean different things: latest_test_date is the newest test inside that 28-day window (so at most yesterday's, always), while newest_test_date is when the domain last actually ran - freshness is judged against the second one.","operationId":"get_synthetic_overview_api_public_v1_domains__domain_id__synthetic_overview_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(google_psi|pod)$"},{"type":"null"}],"title":"Location Type"}},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to one pod. Implies location_type=pod; may be passed alone.","title":"Pod Id"},"description":"Filter to one pod. Implies location_type=pod; may be passed alone."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/tests/{test_id}":{"get":{"tags":["Synthetic"],"summary":"Get full results for one synthetic test","description":"Complete Lighthouse output for a single test run: scores, metrics, audits, opportunities, network waterfall, console logs, and screenshots where still available. raw_data_available is false past the 12-month raw-data TTL; screenshots_available is false past the 90-day screenshot TTL. The internal API calls this endpoint '/test/{id}' (singular); this one is 'tests/{id}' for consistency with its sibling collections. Each audit carries the Lighthouse 'category' it is scored under, so a low best_practices_score can be traced to the audits behind it instead of guessed at from one flat list; audit_categories_notice says why when that mapping was unavailable. Opportunities here are one run's snapshot - two runs of the same domain hours apart legitimately surface disjoint lists, so use GET /synthetic/opportunities for 'what should we fix on the site'.","operationId":"get_synthetic_test_api_public_v1_domains__domain_id__synthetic_tests__test_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"test_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Test Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticTestDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/timeline":{"get":{"tags":["Synthetic"],"summary":"Get the synthetic test timeline","description":"Individual Lighthouse test runs, newest first, up to 36 months of history and 'limit' rows. Defaults to the last 30 days. Not cursor-paginated - 'next_cursor' is always null; if 'has_more' comes back true, request a larger 'limit' for the rest. latest_test_age_days/is_stale describe the newest entry against its own test_frequency - populated when url_id and/or location_type/pod_id narrow this call to one cadence, null otherwise.","operationId":"get_synthetic_timeline_api_public_v1_domains__domain_id__synthetic_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"strategy","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(mobile|desktop|all)$"},{"type":"null"}],"title":"Strategy"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Scope to one monitored URL.","title":"Url Id"},"description":"Scope to one monitored URL."},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter to one testing location type: google_psi or pod.","title":"Location Type"},"description":"Filter to one testing location type: google_psi or pod."},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to one pod. Implies location_type=pod; may be passed alone.","title":"Pod Id"},"description":"Filter to one pod. Implies location_type=pod; may be passed alone."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to 30 days before end_date.","title":"Start Date"},"description":"Defaults to 30 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to now.","title":"End Date"},"description":"Defaults to now."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticTimelineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/trends":{"get":{"tags":["Synthetic"],"summary":"Get synthetic metric trends over time","description":"Raw metric values over time for one strategy, grouped by metric_name (e.g. largest-contentful-paint, server-response-time, cumulative-layout-shift), one item per metric in 'items'. Defaults to the last 30 days. Not paginated, so 'has_more' is always false.","operationId":"get_synthetic_trends_api_public_v1_domains__domain_id__synthetic_trends_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","pattern":"^(mobile|desktop)$","default":"mobile","title":"Strategy"}},{"name":"url_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Url Id"}},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(google_psi|pod)$"},{"type":"null"}],"title":"Location Type"}},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to 30 days before end_date.","title":"Start Date"},"description":"Defaults to 30 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to now.","title":"End Date"},"description":"Defaults to now."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_SyntheticMetricTrend_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls":{"get":{"tags":["Synthetic"],"summary":"List monitored synthetic URLs","description":"URLs currently tested with Lighthouse, in 'items', with each URL's latest score and test count. Paginated with 'page'/'page_size', not 'cursor' - 'next_cursor' is always null; 'has_more' reflects whether 'page' is before the last page. is_stale flags a URL whose latest_test_date is older than twice its own test_frequency - a location that is is_active=true and still 'testing' on paper but has stopped actually reporting. Check it before comparing two URLs' or two location_type calls' scores as a quality difference.","operationId":"list_synthetic_urls_api_public_v1_domains__domain_id__synthetic_urls_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive substring match on the URL.","title":"Search"},"description":"Case-insensitive substring match on the URL."},{"name":"has_data","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter to URLs with (true) or without (false) at least one test.","title":"Has Data"},"description":"Filter to URLs with (true) or without (false) at least one test."},{"name":"location_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(google_psi|pod)$"},{"type":"null"}],"title":"Location Type"}},{"name":"pod_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/bulk":{"post":{"tags":["Synthetic"],"summary":"Add multiple URLs to synthetic monitoring","description":"Adds up to 1000 URLs in one call, all sharing the same test frequency and locations. Fills the workspace's remaining plan slots; URLs beyond that are reported as failed rather than rejecting the whole request.","operationId":"bulk_add_synthetic_urls_api_public_v1_domains__domain_id__synthetic_urls_bulk_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlBulkCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlBulkCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/bulk-delete":{"post":{"tags":["Synthetic"],"summary":"Remove multiple URLs from synthetic monitoring","description":"Deletes up to 200 URLs in one call. Ids that do not belong to this domain are reported as per-id errors.","operationId":"bulk_delete_synthetic_urls_api_public_v1_domains__domain_id__synthetic_urls_bulk_delete_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBulkDeleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBulkDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/bulk-frequency":{"post":{"tags":["Synthetic"],"summary":"Update test frequency for multiple URLs","description":"Updates up to 200 URLs in one call, after checking the new frequency against the workspace's plan.","operationId":"bulk_update_synthetic_frequency_api_public_v1_domains__domain_id__synthetic_urls_bulk_frequency_post","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBulkFrequencyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBulkFrequencyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/{url_id}":{"delete":{"tags":["Synthetic"],"summary":"Remove a URL from synthetic monitoring","description":"Deletes the URL and its location configuration. Collected test history in ClickHouse is retained under its own TTL.","operationId":"delete_synthetic_url_api_public_v1_domains__domain_id__synthetic_urls__url_id__delete","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Synthetic"],"summary":"Get a monitored synthetic URL","operationId":"get_synthetic_url_api_public_v1_domains__domain_id__synthetic_urls__url_id__get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/{url_id}/frequency":{"put":{"tags":["Synthetic"],"summary":"Update the test frequency for a monitored URL","description":"Changes how often Lighthouse tests run for this URL. Available frequencies depend on the workspace's plan.","operationId":"update_synthetic_url_frequency_api_public_v1_domains__domain_id__synthetic_urls__url_id__frequency_put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticFrequencyUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/synthetic/urls/{url_id}/locations":{"get":{"tags":["Synthetic"],"summary":"Get the configured locations for a monitored URL","description":"The full location set for this URL, in 'items'. Not paginated, so 'has_more' is always false.","operationId":"get_synthetic_url_locations_api_public_v1_domains__domain_id__synthetic_urls__url_id__locations_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlLocationsPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Synthetic"],"summary":"Set the test locations for a monitored URL","description":"Replaces the full set of locations tests run from for this URL. The number of locations is capped by the workspace's plan; the free plan may only use Google's Servers.","operationId":"update_synthetic_url_locations_api_public_v1_domains__domain_id__synthetic_urls__url_id__locations_put","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"url_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Url Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUpdateLocationsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticUrlLocationsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/availability":{"get":{"tags":["Uptime"],"summary":"Uptime availability percentage","description":"Uptime % and average response time over the window. Defaults to the trailing 30 days. Pass period='last_month' for the last COMPLETE calendar month instead - that is a different window from the trailing 30 days, not a synonym for it, and the two genuinely disagree (96.74 vs 98.89 per cent on the same domain). Set compare_previous=true for a second window to compare against - compare_mode picks 'previous_period' (immediately preceding window of equal length) or 'calendar_month' (the full calendar month before the one the window ends in). With a calendar-month period the comparison defaults to 'calendar_month', because the 31 days preceding July are not June. Both windows' exact start/end dates are always in the response, so which one produced any verdict is never ambiguous - GET /health's own uptime comparison is a separate fixed trailing 28-day window and can disagree with either mode here.","operationId":"get_availability_api_public_v1_domains__domain_id__uptime_availability_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"compare_previous","in":"query","required":false,"schema":{"type":"boolean","description":"Also return a comparison window - see compare_mode.","default":false,"title":"Compare Previous"},"description":"Also return a comparison window - see compare_mode."},{"name":"compare_mode","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(previous_period|calendar_month)$"},{"type":"null"}],"description":"Only used when compare_previous=true. 'previous_period': immediately preceding window of equal length. 'calendar_month': the full calendar month before the month the current window ends in. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'.","title":"Compare Mode"},"description":"Only used when compare_previous=true. 'previous_period': immediately preceding window of equal length. 'calendar_month': the full calendar month before the month the current window ends in. Defaults to 'calendar_month' when period names a calendar month, otherwise 'previous_period'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeAvailabilityPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/config":{"get":{"tags":["Uptime"],"summary":"Get uptime monitoring configuration","description":"Check interval, timeout, and enabled/alerting state. Plan defaults if none saved.","operationId":"get_config_api_public_v1_domains__domain_id__uptime_config_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeConfigPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/incidents":{"get":{"tags":["Uptime"],"summary":"Downtime incidents","description":"Detailed incident list, newest first. Defaults to the trailing 30 days when start_date/end_date/period are all omitted - the same default GET /uptime/availability uses - and the window actually searched is always echoed back as start_date/end_date, so it is never left to be inferred. Pass period='this_month' (or another named period) for a calendar window instead of the trailing default. error_class filters to one of '5xx'|'4xx'|'timeout'|'dns'|'tls'|'other'; class_counts always reflects the full window regardless of that filter, unless the 2000-incident cap below was hit, in which case class_counts (like the incidents themselves) only covers the most recent 2000. An error_class passed explicitly appears in class_counts even as 0 when the full window was searched, so 'no incidents of this class in this window' is distinguishable from 'this class was never counted' - but once the cap below truncates the fetch, an error_class absent from class_counts means it fell outside the fetched slice, not a confirmed zero, and is left out rather than reported as 0. The window is capped at the most recent 2000 incidents - once that cap is hit, 'total' is omitted (it is no longer a real count) and 'has_more' stays true even past the last page this endpoint can serve, because older incidents in a wide range are not reachable through paging.","operationId":"get_incidents_api_public_v1_domains__domain_id__uptime_incidents_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to 30 days before end_date.","title":"Start Date"},"description":"Defaults to 30 days before end_date."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to now.","title":"End Date"},"description":"Defaults to now."},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."},{"name":"error_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(5xx|4xx|timeout|dns|tls|other)$"},{"type":"null"}],"description":"Restrict to one class. Present in the response's class_counts as 0 when the full window was searched, so a zero result in this window reads differently from a class that was never looked at - but if the 2000-incident fetch cap truncated the window, an absent key means unknown for this window, not zero.","title":"Error Class"},"description":"Restrict to one class. Present in the response's class_counts as 0 when the full window was searched, so a zero result in this window reads differently from a class that was never looked at - but if the 2000-incident fetch cap truncated the window, an absent key means unknown for this window, not zero."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Default 50, max 1000.","title":"Limit"},"description":"Default 50, max 1000."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentsPagePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/response-times":{"get":{"tags":["Uptime"],"summary":"DNS/connect/TLS/total response time breakdown","description":"One row per check in the window, each split into DNS, connect, TLS, and total time, in 'items'. Defaults to 30 days, capped at 10000 rows server-side; if 'has_more' comes back true, request a narrower window for a domain with many checks.","operationId":"get_response_times_api_public_v1_domains__domain_id__uptime_response_times_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_ResponseTimePointPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/slow-checks":{"get":{"tags":["Uptime"],"summary":"Slow but successful checks","description":"Successful checks slower than the window's 95th percentile response time - 'soft incidents' where the site stayed up but was slow. Universe is capped at the 100 slowest checks in the window; paginate within that cap.","operationId":"get_slow_checks_api_public_v1_domains__domain_id__uptime_slow_checks_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":7,"title":"Days"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Default 50, max 100.","title":"Limit"},"description":"Default 50, max 100."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlowChecksPagePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/stats-extra":{"get":{"tags":["Uptime"],"summary":"Response-time percentiles, MTTR, and slow-check count","description":"p50/p75/p95/p99 response time, mean time to recovery, and downtime totals over the window. Defaults to 30 days.","operationId":"get_stats_extra_api_public_v1_domains__domain_id__uptime_stats_extra_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsExtraPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/status":{"get":{"tags":["Uptime"],"summary":"Current uptime status","description":"Result of the single most recent check performed by the uptime monitor. Always live, never cached. 404 if the monitor has not recorded a check for this domain yet.","operationId":"get_status_api_public_v1_domains__domain_id__uptime_status_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeStatusPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/domains/{domain_id}/uptime/timeline":{"get":{"tags":["Uptime"],"summary":"Uptime check timeline, bucketed","description":"Bucketed check history for charting, in 'items'. Defaults to the last 30 days; granularity controls bucket width from 30sec up to month. Pass period='last_month' for the last COMPLETE calendar month instead of a trailing window - see GET /uptime/availability for how much the two can disagree by. Bucket count is capped at 5000 - a fine granularity over a wide window keeps the most recent buckets and drops older ones, while total_checks/uptime_pct always reflect the full window regardless of that cap. Not cursor-paginated; if 'has_more' comes back true, request a coarser granularity for a wider window. Buckets are not uniformly covered: each carries expected_checks/coverage_pct/partial_coverage so a thin bucket is not read as a real movement, and missing_buckets counts the intervals where no check ran at all rather than leaving them as an invisible gap.","operationId":"get_timeline_api_public_v1_domains__domain_id__uptime_timeline_get","parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Domain Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to 30 days before end_date. Clamped to 36 months back.","title":"Start Date"},"description":"Defaults to 30 days before end_date. Clamped to 36 months back."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Defaults to now.","title":"End Date"},"description":"Defaults to now."},{"name":"granularity","in":"query","required":false,"schema":{"type":"string","pattern":"^(30sec|minute|5min|hour|8hour|day|week|month)$","default":"hour","title":"Granularity"}},{"name":"period","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(last_7_days|last_30_days|last_90_days|this_week|last_week|this_month|last_month)$"},{"type":"null"}],"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date.","title":"Period"},"description":"A named reporting period, resolved server-side, as an alternative to naming start_date/end_date yourself. 'last_month' is the last COMPLETE calendar month - not month-to-date and not the trailing 30 days, which are all different windows and can differ by whole percentage points. 'this_month' is month-to-date. 'last_week' is the last complete Monday-to-Sunday week. The trailing windows ('last_7_days', 'last_30_days', 'last_90_days') mean exactly what they say and end today. The resolved dates are always echoed in the response. Cannot be combined with start_date/end_date."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeTimelinePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/health":{"get":{"tags":["Meta"],"summary":"Service health","description":"Unauthenticated liveness probe for the public API surface.","operationId":"health_api_public_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthCheckResponse"}}}}}}},"/api/public/v1/me":{"get":{"tags":["Meta"],"summary":"Describe the calling credential","description":"Returns the identity, workspaces, and permissions behind the credential making this request. Use 'effective_scopes' rather than 'scopes' when deciding what operations to attempt: 'scopes' is what the credential was granted, while 'effective_scopes' is that set intersected with the creator's current workspace role, which is what will actually be enforced.","operationId":"describe_credential_api_public_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialInfo"}}}}}}},"/api/public/v1/notifications":{"get":{"tags":["Notifications"],"summary":"List notifications","description":"The caller's own in-app notifications, newest first. Alert-backed rows (type='alert') carry alert_rule_id/alert_history_id - fetch the rule via GET .../alerts/rules/{domain_id}/{id} and the live graph via GET .../notifications/{id}/trend.","operationId":"list_notifications_api_public_v1_notifications_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread Only"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/NotificationType"},{"type":"null"}],"description":"Filter to one notification type.","title":"Type"},"description":"Filter to one notification type."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_NotificationPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/domain-subscriptions":{"get":{"tags":["Notifications"],"summary":"List per-domain email subscriptions","description":"One entry per domain this credential can access, defaulting to weekly_summary_enabled=true for a domain with no explicit preference row yet.","operationId":"get_domain_email_subscriptions_api_public_v1_notifications_domain_subscriptions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_DomainEmailSubscriptionPublic_"}}}}}},"put":{"tags":["Notifications"],"summary":"Update a per-domain email subscription","operationId":"update_domain_email_subscription_api_public_v1_notifications_domain_subscriptions_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEmailSubscriptionUpdatePublic"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainEmailSubscriptionPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/preferences":{"get":{"tags":["Notifications"],"summary":"Get notification preferences","description":"Creates and returns default preferences on first read if the caller has none yet.","operationId":"get_notification_preferences_api_public_v1_notifications_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesPublic"}}}}}},"put":{"tags":["Notifications"],"summary":"Update notification preferences","description":"Partial update - only the fields supplied are changed.","operationId":"update_notification_preferences_api_public_v1_notifications_preferences_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesUpdatePublic"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/read":{"post":{"tags":["Notifications"],"summary":"Mark notifications as read","description":"Marks the given notifications read, or every unread notification when notification_ids is omitted.","operationId":"mark_notifications_read_api_public_v1_notifications_read_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadRequestPublic"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadResponsePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/stats":{"get":{"tags":["Notifications"],"summary":"Get notification statistics","description":"Counts by type and severity for the caller's own notifications, plus how many arrived in the last 24 hours.","operationId":"get_notification_stats_api_public_v1_notifications_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationStatsPublic"}}}}}}},"/api/public/v1/notifications/unread-count":{"get":{"tags":["Notifications"],"summary":"Get the unread notification count","operationId":"get_unread_count_api_public_v1_notifications_unread_count_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadCountPublic"}}}}}}},"/api/public/v1/notifications/{notification_id}":{"delete":{"tags":["Notifications"],"summary":"Delete a notification","operationId":"delete_notification_api_public_v1_notifications__notification_id__delete","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/{notification_id}/read":{"post":{"tags":["Notifications"],"summary":"Mark one notification as read","operationId":"mark_single_notification_read_api_public_v1_notifications__notification_id__read_post","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadResponsePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/{notification_id}/trend":{"get":{"tags":["Notifications"],"summary":"Get the live trend behind a fired alert","description":"Recomputes the time series behind an alert-backed notification's graph from current data. Only notifications with an alert_rule_id are graphable; others 404, as does a notification whose module/metric has no trend query.","operationId":"get_notification_trend_api_public_v1_notifications__notification_id__trend_get","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationTrendPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/notifications/{notification_id}/unread":{"post":{"tags":["Notifications"],"summary":"Mark one notification as unread","operationId":"mark_single_notification_unread_api_public_v1_notifications__notification_id__unread_post","parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Notification Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponsePublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/oauth/authorize":{"get":{"tags":["OAuth"],"summary":"Start an authorization request","description":"Validates the request and redirects the browser to the VitalSentinel consent screen carrying a signed, ten-minute request handle. Never renders HTML itself. PKCE with code_challenge_method=S256 is mandatory, and the RFC 8707 'resource' parameter is required: the token is bound to that audience and is rejected by any other API. Failures caused by an unknown client or an unregistered redirect_uri are returned to the caller; every other failure is reported by redirecting back to the registered redirect_uri with an 'error' parameter.","operationId":"authorize_api_public_v1_oauth_authorize_get","parameters":[{"name":"response_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Response Type"}},{"name":"client_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Client Id"}},{"name":"redirect_uri","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Redirect Uri"}},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Scope"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":1024},{"type":"null"}],"title":"State"}},{"name":"code_challenge","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Code Challenge Method"}},{"name":"resource","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Required. Exactly one value; the audience the token will be bound to.","title":"Resource"},"description":"Required. Exactly one value; the audience the token will be bound to."}],"responses":{"302":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/oauth/authorize/consent":{"post":{"tags":["OAuth"],"summary":"Record a consent decision","description":"Called by the VitalSentinel consent page with the user's dashboard session. On approval this records the grant and mints a 60-second single-use authorization code; on denial it returns the RFC 6749 access_denied redirect. Either way the response carries the URL to navigate the browser to - do not fetch it.","operationId":"record_consent_api_public_v1_oauth_authorize_consent_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentDecision"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/public/v1/oauth/authorize/request":{"get":{"tags":["OAuth"],"summary":"Resolve an authorization request handle","description":"Called by the VitalSentinel consent page with the user's dashboard session. Returns everything the screen renders: the client and whether it is verified, the redirect URI in full, the requested scopes in plain language grouped by risk, and the workspaces the signed-in user may include. Not part of the third-party API surface.","operationId":"describe_authorization_request_api_public_v1_oauth_authorize_request_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request","in":"query","required":true,"schema":{"type":"string","maxLength":4096,"title":"Request"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentRequestInfo"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/oauth/register":{"post":{"tags":["OAuth"],"summary":"Register an OAuth client","description":"RFC 7591 dynamic client registration. Open by design so an MCP client can self-register on first use. Registered clients start unverified and are shown as such on the consent screen, with their redirect URI in full. Redirect URIs must be absolute https, except http on 127.0.0.1, ::1, or localhost for desktop clients. Rate-limited per source address.","operationId":"register_client_api_public_v1_oauth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientRegistrationRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientRegistrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/oauth/revoke":{"post":{"tags":["OAuth"],"summary":"Revoke a token","description":"RFC 7009. Accepts either a refresh token or an access token and revokes the whole grant behind it, including every access token already issued - refusing future refreshes alone would leave up to an hour of live access. Always returns 200, including for an unknown token, so the endpoint cannot be used to test whether a token exists.","operationId":"revoke_token_api_public_v1_oauth_revoke_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_revoke_token_api_public_v1_oauth_revoke_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevocationResult"}}}},"401":{"description":"Client authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange an authorization code or refresh token","description":"Supports grant_type=authorization_code and grant_type=refresh_token only. Authorization codes are single-use and expire after 60 seconds. Refresh tokens are single-use and rotate on every call: presenting one that has already been rotated away revokes the entire grant and every access token issued under it, because the only way to hold a superseded refresh token is to have obtained a copy of it. Errors follow RFC 6749 \u00a75.2, not application/problem+json.","operationId":"issue_token_api_public_v1_oauth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_issue_token_api_public_v1_oauth_token_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"RFC 6749 \u00a75.2 error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthErrorResponse"}}}},"401":{"description":"Client authentication failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces":{"get":{"tags":["Workspaces and domains"],"summary":"List accessible workspaces","description":"Workspaces this credential can access. For an API key that is always exactly one; for an OAuth grant it is the set the user selected when they approved the connection, which is not necessarily every workspace they belong to. Returned as a single page today (next_cursor is always null) - the envelope leaves room to paginate without a breaking change once that stops being true.","operationId":"list_workspaces_api_public_v1_workspaces_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_WorkspaceSummary_"}}}}}}},"/api/public/v1/workspaces/{workspace_id}":{"get":{"tags":["Workspaces and domains"],"summary":"Get a workspace","operationId":"get_workspace_api_public_v1_workspaces__workspace_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/ga/connections":{"get":{"tags":["Google Analytics"],"summary":"GA property and token health for every domain in a workspace","description":"Bulk, workspace-scoped sibling of GET /{domain_id}/ga/properties/linked: one Postgres query covering every domain in workspace_id, instead of one linked-property call per domain. A domain with no GA4 property linked ('ga_property_id' null) is distinct from one that is linked but not syncing ('account_token_valid' false) - that distinction is the point of this endpoint, so 'which of my sites have Google Analytics connected, and are they actually live' does not require inspecting every domain individually. Every domain in the workspace is included, not just the linked ones.","operationId":"list_connections_api_public_v1_workspaces__workspace_id__ga_connections_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GAConnectionsPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/quota":{"get":{"tags":["Inspector"],"summary":"Get Inspector quota usage","description":"Runs used and remaining in the workspace's current monthly billing cycle.","operationId":"get_quota_api_public_v1_workspaces__workspace_id__inspector_quota_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InspectorQuotaPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/run":{"post":{"tags":["Inspector"],"summary":"Run an Inspector test","description":"Queues a new on-demand Inspector test for a URL and returns immediately. Poll GET .../tests/{job_id} until status reaches 'completed' or 'failed'. Consumes one unit of the workspace's monthly Inspector quota - reported back via quota_remaining/quota_limit/quota_resets_on - and requires an Idempotency-Key header so a retry does not double-charge it.","operationId":"run_inspection_api_public_v1_workspaces__workspace_id__inspector_run_post","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InspectorRunRequestPublic"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/tests":{"get":{"tags":["Inspector"],"summary":"List Inspector tests","description":"Paginated history of on-demand Inspector runs in this workspace, newest first. Each item is a summary only - call GET .../tests/{id} for the full check and issue detail.","operationId":"list_tests_api_public_v1_workspaces__workspace_id__inspector_tests_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Page size. Defaults to 50, max 1000.","title":"Limit"},"description":"Page size. Defaults to 50, max 1000."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor from a previous page's next_cursor.","title":"Cursor"},"description":"Opaque cursor from a previous page's next_cursor."},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":500,"description":"Case-insensitive substring match on the tested URL.","default":"","title":"Search"},"description":"Case-insensitive substring match on the tested URL."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page_InspectorTestSummaryPublic_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/tests/{test_id}":{"get":{"tags":["Inspector"],"summary":"Get an Inspector test","description":"Full detail for one Inspector test: every check result (with its raw payload, read from ClickHouse) and every issue the issues engine found, plus the pillar/sub-pillar score breakdown.","operationId":"get_test_api_public_v1_workspaces__workspace_id__inspector_tests__test_id__get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"test_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Test Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InspectorTestDetailPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/tests/{test_id}/checks/{check_type}/data":{"get":{"tags":["Inspector"],"summary":"Get the raw payload for one check","description":"The full, un-stripped result for a single check (crux, uptime, ssl, whois, robots, chrome), read from ClickHouse. Shape varies by check_type - this is for on-demand access to bulky artefacts (HAR, Chrome trace, raw PSI/CrUX responses) that GET .../tests/{id} keeps out of the main response.","operationId":"get_check_data_api_public_v1_workspaces__workspace_id__inspector_tests__test_id__checks__check_type__data_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"test_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Test Id"}},{"name":"check_type","in":"path","required":true,"schema":{"type":"string","title":"Check Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Check Data Api Public V1 Workspaces  Workspace Id  Inspector Tests  Test Id  Checks  Check Type  Data Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/inspector/tests/{test_id}/previous":{"get":{"tags":["Inspector"],"summary":"Get the previous Inspector test for the same URL","description":"Slim summary of the previous completed inspection for the same URL and device mode as test_id, for computing score deltas. Returns 204 with no body when there is no previous run (including when test_id itself does not resolve).","operationId":"get_previous_test_api_public_v1_workspaces__workspace_id__inspector_tests__test_id__previous_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}},{"name":"test_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Test Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InspectorPreviousTestSummaryPublic"}}}},"204":{"description":"No previous run exists for this URL + device mode"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public/v1/workspaces/{workspace_id}/rum/has-data":{"get":{"tags":["RUM"],"summary":"Whether each domain in a workspace has any RUM data","description":"Bulk, workspace-scoped sibling of GET /{domain_id}/rum/has-data: one grouped ClickHouse query covering every domain in workspace_id, instead of one has-data call per domain. RUM has no configuration row (see list_domains' active_modules docstring), so it never appears there even on a domain streaming millions of events - 'which of my sites have RUM reporting' otherwise costs one call per domain, with no way to batch it. Every domain in the workspace is included, each with its own has_data flag, not just the ones with data.","operationId":"get_has_data_bulk_api_public_v1_workspaces__workspace_id__rum_has_data_get","parameters":[{"name":"workspace_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Workspace Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RumHasDataBulkPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AIBotEntryPublic":{"properties":{"name":{"type":"string","title":"Name"},"vendor":{"type":"string","title":"Vendor"},"purpose":{"type":"string","title":"Purpose","description":"'training', 'retrieval', 'preview', or 'agent'."},"allowed":{"type":"boolean","title":"Allowed"},"matched_rule":{"type":"string","title":"Matched Rule"},"has_specific_group":{"type":"boolean","title":"Has Specific Group"},"bypasses_wildcard":{"type":"boolean","title":"Bypasses Wildcard"},"crawl_delay_declared":{"type":"string","title":"Crawl Delay Declared","default":""},"honors_crawl_delay":{"type":"boolean","title":"Honors Crawl Delay","default":true},"docs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Docs Url"}},"type":"object","required":["name","vendor","purpose","allowed","matched_rule","has_specific_group","bypasses_wildcard"],"title":"AIBotEntryPublic"},"AIBotGovernancePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"bots":{"items":{"$ref":"#/components/schemas/AIBotEntryPublic"},"type":"array","title":"Bots"},"declared_but_empty":{"items":{"type":"string"},"type":"array","title":"Declared But Empty","description":"Bots with their own user-agent group that has no Allow/Disallow rules."},"presets":{"items":{"$ref":"#/components/schemas/AIBotPresetPublic"},"type":"array","title":"Presets"}},"type":"object","required":["domain_id","bots","presets"],"title":"AIBotGovernancePublic"},"AIBotPresetPublic":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"description":{"type":"string","title":"Description"},"snippet":{"type":"string","title":"Snippet","description":"robots.txt text to paste in manually - never applied for you."}},"type":"object","required":["id","label","description","snippet"],"title":"AIBotPresetPublic"},"ActiveCountryPublic":{"properties":{"country_code":{"type":"string","title":"Country Code"},"visitors":{"type":"integer","title":"Visitors","default":0}},"type":"object","required":["country_code"],"title":"ActiveCountryPublic"},"ActivePagePublic":{"properties":{"path":{"type":"string","title":"Path"},"visitors":{"type":"integer","title":"Visitors","default":0}},"type":"object","required":["path"],"title":"ActivePagePublic"},"AlertHistoryPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"perex":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Perex","description":"Short summary shown in the notification list."},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Full content shown in the detail view."},"severity":{"$ref":"#/components/schemas/NotificationSeverity"},"is_read":{"type":"boolean","title":"Is Read"},"read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Read At"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"domain_name":{"type":"string","title":"Domain Name"},"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"workspace_name":{"type":"string","title":"Workspace Name"},"alert_rule_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Alert Rule Id","description":"Fetch the rule via GET /domains/{domain_id}/alerts/rules/{alert_rule_id}."},"alert_history_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Alert History Id"},"action_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","severity","is_read","domain_id","domain_name","workspace_id","workspace_name","created_at"],"title":"AlertHistoryPublic","description":"One fired alert - an in-app notification of ``type='alert'`` - as returned by\n``GET /alerts/history`` (and its domain-scoped twin in ``notifications.py``).\n\nCarries ``domain_id``/``domain_name`` and ``workspace_id``/``workspace_name`` so a\ncaller listing across every accessible domain never has to look either up in a\nsecond call - the entire point of the account-wide route over looping\n``GET /domains/{domain_id}/alerts/history``.\n\n``severity`` is the four-value notification vocabulary, not the three-value\n``AlertSeverityPublic`` a rule declares: ``alert_resolved`` notifications are denormalised\nfrom the ``notification_templates`` row at write time with severity ``'success'``, which\nno ``AlertRule.severity`` ever holds."},"AlertMetricDefinitionPublic":{"properties":{"value":{"type":"string","title":"Value","description":"Pass this as AlertRuleCreate.metric."},"label":{"type":"string","title":"Label"},"unit":{"type":"string","title":"Unit","description":"Display unit, e.g. 'ms', '%', 'days'. Empty string for unitless."},"default_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Default Threshold"},"default_cooldown":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Default Cooldown","description":"Suggested cooldown_minutes if the rule does not specify one."},"pct_only":{"type":"boolean","title":"Pct Only","description":"True when this metric only supports percentage-change/anomaly operators.","default":false}},"type":"object","required":["value","label","unit"],"title":"AlertMetricDefinitionPublic","description":"One metric a rule can be built on, for a given module."},"AlertModule":{"type":"string","enum":["uptime","rum","crux","synthetic","robots_txt","indexing","gsc","ga","web_analytics"],"title":"AlertModule","description":"Monitoring modules that support alerts"},"AlertRuleCreatePublic":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"module":{"$ref":"#/components/schemas/AlertModule"},"metric":{"type":"string","maxLength":100,"minLength":1,"title":"Metric"},"condition_operator":{"$ref":"#/components/schemas/ConditionOperator"},"threshold_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold Value","description":"Threshold to compare against. Null only for the 'changed' operator."},"comparison_window":{"anyOf":[{"$ref":"#/components/schemas/ComparisonWindow"},{"type":"null"}],"description":"Required for pct_decrease / pct_increase / anomaly operators."},"form_factor":{"anyOf":[{"$ref":"#/components/schemas/FormFactor"},{"type":"null"}]},"url_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Url Id","description":"Restrict the rule to one monitored URL. Only valid for crux and synthetic."},"evaluation_window":{"anyOf":[{"$ref":"#/components/schemas/EvaluationWindow"},{"type":"null"}],"description":"Synthetic only: average over the last 1/3/5/10 test runs."},"severity":{"$ref":"#/components/schemas/AlertSeverity","default":"warning"},"sustained_minutes":{"type":"integer","maximum":1440.0,"minimum":0.0,"title":"Sustained Minutes","default":0},"cooldown_minutes":{"anyOf":[{"type":"integer","maximum":10080.0,"minimum":0.0},{"type":"null"}],"title":"Cooldown Minutes","description":"Minimum minutes between repeat firings. Defaults per-metric."},"enabled":{"type":"boolean","title":"Enabled","default":true},"notify_email":{"type":"boolean","title":"Notify Email","description":"Send an email in addition to the in-app notification.","default":false}},"type":"object","required":["name","module","metric","condition_operator"],"title":"AlertRuleCreatePublic","description":"Body of ``POST /domains/{domain_id}/alerts/rules``."},"AlertRulePublic":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"module":{"$ref":"#/components/schemas/AlertModule"},"metric":{"type":"string","maxLength":100,"minLength":1,"title":"Metric"},"condition_operator":{"$ref":"#/components/schemas/ConditionOperator"},"threshold_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold Value","description":"Threshold to compare against. Null only for the 'changed' operator."},"comparison_window":{"anyOf":[{"$ref":"#/components/schemas/ComparisonWindow"},{"type":"null"}],"description":"Required for pct_decrease / pct_increase / anomaly operators."},"form_factor":{"anyOf":[{"$ref":"#/components/schemas/FormFactor"},{"type":"null"}]},"url_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Url Id","description":"Restrict the rule to one monitored URL. Only valid for crux and synthetic."},"evaluation_window":{"anyOf":[{"$ref":"#/components/schemas/EvaluationWindow"},{"type":"null"}],"description":"Synthetic only: average over the last 1/3/5/10 test runs."},"severity":{"$ref":"#/components/schemas/AlertSeverity","default":"warning"},"sustained_minutes":{"type":"integer","maximum":1440.0,"minimum":0.0,"title":"Sustained Minutes","default":0},"cooldown_minutes":{"anyOf":[{"type":"integer","maximum":10080.0,"minimum":0.0},{"type":"null"}],"title":"Cooldown Minutes","description":"Minimum minutes between repeat firings. Defaults per-metric."},"enabled":{"type":"boolean","title":"Enabled","default":true},"notify_email":{"type":"boolean","title":"Notify Email","description":"Send an email in addition to the in-app notification.","default":false},"id":{"type":"string","format":"uuid","title":"Id"},"user_id":{"type":"string","format":"uuid","title":"User Id","description":"Rule owner. Alert rules are personal, not shared team config."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"domain_name":{"type":"string","title":"Domain Name","description":"Hostname of the domain this rule watches - no second lookup needed."},"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"workspace_name":{"type":"string","title":"Workspace Name"},"last_triggered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Triggered At"},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At"},"current_state":{"$ref":"#/components/schemas/AlertState","default":"ok"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["name","module","metric","condition_operator","id","user_id","domain_id","domain_name","workspace_id","workspace_name","created_at","updated_at"],"title":"AlertRulePublic","description":"One configured alert rule.\n\nCarries ``domain_name`` and ``workspace_id``/``workspace_name`` for the same reason\n``AlertHistoryPublic`` does: the account-wide ``GET /alerts/rules`` exists so a caller\nnever has to loop ``GET /domains``, and a list of rules identified only by\n``domain_id`` sends it straight back to that loop to answer \"how many rules per site\"."},"AlertRuleUpdatePublic":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"metric":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Metric"},"condition_operator":{"anyOf":[{"$ref":"#/components/schemas/ConditionOperator"},{"type":"null"}]},"threshold_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold Value"},"comparison_window":{"anyOf":[{"$ref":"#/components/schemas/ComparisonWindow"},{"type":"null"}]},"form_factor":{"anyOf":[{"$ref":"#/components/schemas/FormFactor"},{"type":"null"}]},"url_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Url Id"},"evaluation_window":{"anyOf":[{"$ref":"#/components/schemas/EvaluationWindow"},{"type":"null"}]},"severity":{"anyOf":[{"$ref":"#/components/schemas/AlertSeverity"},{"type":"null"}]},"sustained_minutes":{"anyOf":[{"type":"integer","maximum":1440.0,"minimum":0.0},{"type":"null"}],"title":"Sustained Minutes"},"cooldown_minutes":{"anyOf":[{"type":"integer","maximum":10080.0,"minimum":0.0},{"type":"null"}],"title":"Cooldown Minutes"},"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"notify_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Email"}},"type":"object","title":"AlertRuleUpdatePublic","description":"Body of ``PUT /domains/{domain_id}/alerts/rules/{rule_id}``. All fields optional."},"AlertSeverity":{"type":"string","enum":["info","warning","critical"],"title":"AlertSeverity","description":"Alert severity levels"},"AlertState":{"type":"string","enum":["ok","pending","firing","resolved"],"title":"AlertState","description":"Alert states"},"AnalyticsComparisonPublic":{"properties":{"visitors":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"pageviews":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"sessions":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"bounce_rate_pct":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]},"avg_duration_seconds":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]}},"type":"object","title":"AnalyticsComparisonPublic"},"AnalyticsConfigPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"currency":{"type":"string","title":"Currency","default":"USD"},"store_page_titles":{"type":"boolean","title":"Store Page Titles","description":"Whether page titles are retained (DPIA action N9).","default":true},"store_search_terms":{"type":"boolean","title":"Store Search Terms","description":"Whether site-search query terms are retained (DPIA action N9).","default":true}},"type":"object","required":["domain_id"],"title":"AnalyticsConfigPublic"},"AnalyticsConfigUpdateRequest":{"properties":{"currency":{"anyOf":[{"type":"string","maxLength":3,"minLength":3,"pattern":"^[A-Z]{3}$"},{"type":"null"}],"title":"Currency"},"store_page_titles":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Store Page Titles"},"store_search_terms":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Store Search Terms"}},"type":"object","title":"AnalyticsConfigUpdateRequest"},"AnalyticsMetricsComparisonPublic":{"properties":{"visitors":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Visitors"},"pageviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pageviews"},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"bounce_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate Pct"},"avg_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Duration Seconds"},"search_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Search Pct"},"mobile_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mobile Pct"},"revenue":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue"},"orders":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Orders"},"avg_order_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Order Value"},"conversion_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Rate Pct"}},"type":"object","title":"AnalyticsMetricsComparisonPublic"},"AnalyticsMetricsPublic":{"properties":{"visitors":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Visitors"},"pageviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pageviews"},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"bounce_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate Pct"},"avg_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Duration Seconds"},"search_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Search Pct","description":"% of sessions from organic search."},"mobile_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mobile Pct","description":"% of sessions from mobile devices."},"revenue":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue","description":"In the workspace's configured currency."},"orders":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Orders"},"avg_order_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Order Value"},"conversion_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Rate Pct","description":"% of sessions that completed a purchase."},"comparison":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"AnalyticsMetricsPublic"},"AnalyticsOverviewPublic":{"properties":{"visitors":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Visitors"},"pageviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pageviews"},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"bounce_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate Pct","description":"Percentage, 0 to 100."},"avg_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Duration Seconds","description":"Mean session length. Sessions over an hour are excluded."}},"type":"object","title":"AnalyticsOverviewPublic"},"AnnotationCreateRequest":{"properties":{"change_at":{"type":"string","format":"date-time","title":"Change At","description":"Must match a changed_at in the changes timeline."},"new_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"New Version Id"},"note":{"type":"string","maxLength":2000,"minLength":1,"title":"Note"}},"type":"object","required":["change_at","note"],"title":"AnnotationCreateRequest"},"AnnotationPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"change_at":{"type":"string","format":"date-time","title":"Change At"},"new_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"New Version Id"},"note":{"type":"string","title":"Note"},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","change_at","note","created_at","updated_at"],"title":"AnnotationPublic"},"AnnotationUpdateRequest":{"properties":{"note":{"type":"string","maxLength":2000,"minLength":1,"title":"Note"}},"type":"object","required":["note"],"title":"AnnotationUpdateRequest"},"AvailableMetricsResponsePublic":{"properties":{"metrics":{"additionalProperties":{"items":{"$ref":"#/components/schemas/AlertMetricDefinitionPublic"},"type":"array"},"type":"object","title":"Metrics"}},"type":"object","required":["metrics"],"title":"AvailableMetricsResponsePublic","description":"The full metric catalogue, keyed by module name."},"Body_issue_token_api_public_v1_oauth_token_post":{"properties":{"grant_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grant Type"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"redirect_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Redirect Uri"},"code_verifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code Verifier"},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"},"resource":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"}},"type":"object","title":"Body_issue_token_api_public_v1_oauth_token_post"},"Body_revoke_token_api_public_v1_oauth_revoke_post":{"properties":{"token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"},"token_type_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Type Hint"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"}},"type":"object","title":"Body_revoke_token_api_public_v1_oauth_revoke_post"},"BrowserVersionMetrics":{"properties":{"browser":{"type":"string","title":"Browser"},"version":{"type":"string","title":"Version"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["browser","version"],"title":"BrowserVersionMetrics"},"CatalogSectionPublic":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"icon":{"type":"string","title":"Icon"},"widgets":{"items":{"$ref":"#/components/schemas/CatalogWidgetPublic"},"type":"array","title":"Widgets"}},"type":"object","required":["id","name","description","icon","widgets"],"title":"CatalogSectionPublic"},"CatalogWidgetPublic":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"config_schema":{"additionalProperties":true,"type":"object","title":"Config Schema","description":"Per-field config shape (type, options, default) for building a template UI."}},"type":"object","required":["id","name","description","config_schema"],"title":"CatalogWidgetPublic"},"ChangeImpactPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"changed_at":{"type":"string","format":"date-time","title":"Changed At"},"new_version_id":{"type":"string","format":"uuid","title":"New Version Id"},"newly_blocked_sitemap_count":{"type":"integer","title":"Newly Blocked Sitemap Count","default":0},"newly_allowed_sitemap_count":{"type":"integer","title":"Newly Allowed Sitemap Count","default":0},"newly_blocked_top_pages":{"type":"integer","title":"Newly Blocked Top Pages","default":0},"newly_allowed_top_pages":{"type":"integer","title":"Newly Allowed Top Pages","default":0},"lost_clicks":{"type":"integer","title":"Lost Clicks","default":0},"gained_clicks":{"type":"integer","title":"Gained Clicks","default":0},"newly_blocked_sample":{"items":{"type":"string"},"type":"array","title":"Newly Blocked Sample"},"newly_allowed_sample":{"items":{"type":"string"},"type":"array","title":"Newly Allowed Sample"}},"type":"object","required":["domain_id","changed_at","new_version_id"],"title":"ChangeImpactPublic"},"ChannelMetrics":{"properties":{"channel":{"type":"string","title":"Channel"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0}},"type":"object","required":["channel"],"title":"ChannelMetrics"},"ClientRegistrationRequest":{"properties":{"client_name":{"type":"string","maxLength":255,"minLength":1,"title":"Client Name","description":"Human-readable name, shown on the consent screen."},"redirect_uris":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Redirect Uris","description":"Absolute redirect URIs. Must be https, except for loopback (http://127.0.0.1 or http://localhost) which desktop clients need. Matched exactly at authorization time - never by prefix."},"grant_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Grant Types","description":"Only 'authorization_code' and 'refresh_token' are supported."},"response_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Response Types","description":"Only 'code' is supported."},"token_endpoint_auth_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Endpoint Auth Method","description":"'none' for a public client (the default, and what MCP desktop clients use), or 'client_secret_basic' / 'client_secret_post' to be issued a secret."},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Space-delimited scopes this client will ever request. Requests for anything outside this set are rejected at the authorization endpoint. Omit to leave the client free to request any registered scope."}},"type":"object","required":["client_name","redirect_uris"],"title":"ClientRegistrationRequest","description":"Client metadata submitted to ``POST /oauth/register``.\n\nUnknown members are ignored rather than rejected, per RFC 7591 \u00a73.1: a client that\nsends ``logo_uri`` or ``contacts`` should still register successfully even though we\nstore neither."},"ClientRegistrationResponse":{"properties":{"client_id":{"type":"string","title":"Client Id"},"client_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Secret"},"client_id_issued_at":{"type":"integer","title":"Client Id Issued At","description":"Unix seconds."},"client_secret_expires_at":{"type":"integer","title":"Client Secret Expires At","description":"Always 0: issued secrets do not expire.","default":0},"client_name":{"type":"string","title":"Client Name"},"redirect_uris":{"items":{"type":"string"},"type":"array","title":"Redirect Uris"},"grant_types":{"items":{"type":"string"},"type":"array","title":"Grant Types"},"response_types":{"items":{"type":"string"},"type":"array","title":"Response Types"},"token_endpoint_auth_method":{"type":"string","title":"Token Endpoint Auth Method"},"scope":{"type":"string","title":"Scope"},"verified":{"type":"boolean","title":"Verified","description":"Always false for a self-registered client. The consent screen labels unverified clients and shows the redirect URI in full.","default":false}},"type":"object","required":["client_id","client_id_issued_at","client_name","redirect_uris","grant_types","response_types","token_endpoint_auth_method","scope"],"title":"ClientRegistrationResponse","description":"RFC 7591 \u00a73.2.1 registration response.\n\n``client_secret`` is present only for a confidential client and is returned exactly\nonce - it is stored as a sha256 hash and cannot be recovered."},"ComparisonCoveragePublic":{"properties":{"current_days_requested":{"type":"integer","title":"Current Days Requested","description":"Whole days in the window this call asked about."},"current_days_with_data":{"type":"integer","title":"Current Days With Data","description":"Of those, how many fall at or before the store's freshness date. Fewer than current_days_requested means the tail of your window is empty - not quiet."},"previous_days_requested":{"type":"integer","title":"Previous Days Requested","description":"Whole days in the comparison window."},"previous_days_with_data":{"type":"integer","title":"Previous Days With Data","description":"Of those, how many carry data. Usually all of them, being further back."},"like_for_like":{"type":"boolean","title":"Like For Like","description":"True when both windows are covered to within 5 percentage points of each other, so the percentage change is measuring a real movement. FALSE means the two sides hold materially different amounts of data and the magnitude is an artefact of that - the direction may still be right, the size is not. Do not quote a change_pct to a customer when this is false without saying why."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Set only when like_for_like is false: states the shortfall in plain words, ready to pass on. Null when the two windows are comparable."}},"type":"object","required":["current_days_requested","current_days_with_data","previous_days_requested","previous_days_with_data","like_for_like"],"title":"ComparisonCoveragePublic","description":"How much data each side of a period-over-period comparison actually holds.\n\nCoverage is measured as whole days from the window's start up to the earlier of its end\nand the store's freshness date (``data_through`` / ``last_updated`` on the same\nresponse). That is a *trailing-gap* measure, which is the failure mode that occurs in\npractice - a sync that stopped - and it deliberately does not detect gaps in the middle\nof a window. Where a store can lose a day mid-window and say so, that surface reports it\nseparately: see ``UptimeTimelinePublic.missing_buckets``."},"ComparisonWindow":{"type":"string","enum":["1d","3d","7d","14d","28d"],"title":"ComparisonWindow","description":"Comparison window for percentage-change and anomaly operators"},"ConditionOperator":{"type":"string","enum":["greater_than","less_than","greater_or_equal","less_or_equal","equals","not_equals","changed","pct_decrease","pct_increase","anomaly"],"title":"ConditionOperator","description":"Alert condition operators"},"ConsentDecision":{"properties":{"request":{"type":"string","title":"Request","description":"The signed request handle from the authorize redirect."},"approved":{"type":"boolean","title":"Approved"},"workspace_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Workspace Ids","description":"Workspaces to include in the grant. Required when approved. Must all be workspaces the signed-in user is an accepted member of."},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"Scopes to grant. Must be a subset of what was requested - the user may narrow the request but never widen it. Omit to grant everything requested."}},"type":"object","required":["request","approved"],"title":"ConsentDecision","description":"The consent page's answer, POSTed with the user's dashboard session."},"ConsentRequestInfo":{"properties":{"client_id":{"type":"string","title":"Client Id"},"client_name":{"type":"string","title":"Client Name"},"verified":{"type":"boolean","title":"Verified","description":"False for a self-registered client. Show this prominently: with open dynamic registration, a lookalike client_name is the expected attack and the verification label plus the full redirect URI is the only defence."},"redirect_uri":{"type":"string","title":"Redirect Uri","description":"Show in full. Do not truncate the host."},"resource":{"type":"string","title":"Resource","description":"The API this token will be usable against, and only that."},"scopes":{"items":{"$ref":"#/components/schemas/ConsentScope"},"type":"array","title":"Scopes"},"workspaces":{"items":{"$ref":"#/components/schemas/ConsentWorkspace"},"type":"array","title":"Workspaces","description":"Workspaces the user may include. Never pre-select all of them."},"requires_reconsent":{"type":"boolean","title":"Requires Reconsent","description":"True when no live grant already covers every requested scope. Evaluated on scope alone - it does not know which workspaces the user is about to pick, so False does not by itself mean re-approving is a no-op. Compare the workspaces currently selected against 'already_granted_workspace_ids' before telling the user this is just a reconfirmation; a request for a superset of scopes always re-prompts regardless of workspace."},"already_granted_workspace_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Already Granted Workspace Ids","description":"Workspace IDs covered by the live grant that made requires_reconsent False, if any (always empty when requires_reconsent is True). The consent page recomputes, as the user changes their workspace selection, whether the selection is a subset of this set - only then is 'you already granted this' an accurate thing to tell them."},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds","description":"Remaining validity of the request handle."}},"type":"object","required":["client_id","client_name","verified","redirect_uri","resource","scopes","workspaces","requires_reconsent","expires_in_seconds"],"title":"ConsentRequestInfo","description":"Everything the consent page renders, resolved from the signed request handle."},"ConsentResult":{"properties":{"redirect_to":{"type":"string","title":"Redirect To","description":"Absolute URL. Navigate the browser here; do not fetch it."}},"type":"object","required":["redirect_to"],"title":"ConsentResult","description":"Where the browser must go next.\n\nThe backend never issues the redirect itself here: the caller is a fetch() from the\nconsent page, and a 302 in a fetch response is followed by the browser without the\npage ever seeing it."},"ConsentScope":{"properties":{"scope":{"type":"string","title":"Scope"},"description":{"type":"string","title":"Description","description":"Plain-language label. Show this, not the raw scope."},"risk_tier":{"type":"string","title":"Risk Tier","description":"'read', 'configure', 'run', or 'sensitive_read'."},"detail":{"type":"string","title":"Detail","description":"Long-form explanation of what a grant of this scope can and cannot do, for the consent screen's per-scope help. Same copy as the scope catalog's 'detail'."},"already_granted":{"type":"boolean","title":"Already Granted","description":"Whether a live grant for this client already covers this scope.","default":false}},"type":"object","required":["scope","description","risk_tier","detail"],"title":"ConsentScope","description":"One requested scope, as the consent screen should present it."},"ConsentWorkspace":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role","description":"The user's current role in this workspace."}},"type":"object","required":["id","name","role"],"title":"ConsentWorkspace","description":"A workspace the signed-in user may include in the grant."},"ContinentMetrics":{"properties":{"name":{"type":"string","title":"Name"},"pageviews":{"type":"integer","title":"Pageviews","default":0},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["name"],"title":"ContinentMetrics"},"CoreWebVitalPublic":{"properties":{"metric_name":{"type":"string","title":"Metric Name"},"event_count":{"type":"integer","title":"Event Count"},"avg_value":{"type":"number","title":"Avg Value"},"p50":{"type":"number","title":"P50"},"p75":{"type":"number","title":"P75"},"p95":{"type":"number","title":"P95"},"p99":{"type":"number","title":"P99"},"min_value":{"type":"number","title":"Min Value"},"max_value":{"type":"number","title":"Max Value"}},"type":"object","required":["metric_name","event_count","avg_value","p50","p75","p95","p99","min_value","max_value"],"title":"CoreWebVitalPublic","description":"One metric's aggregate for one device type.\n\n``avg_value``/``p50``/``p75``/``p95``/``p99``/``min_value``/``max_value`` are in\nmilliseconds for LCP, INP, FCP, and TTFB; CLS values are a unitless score. The unit\ndepends on ``metric_name``, which is why it is not baked into the field name here."},"CoverageSummary":{"properties":{"inspected_urls":{"type":"integer","title":"Inspected Urls","description":"URLs with a current inspection verdict."},"indexed_urls":{"type":"integer","title":"Indexed Urls","description":"Of those, how many Google reports as indexed."},"not_indexed_urls":{"type":"integer","title":"Not Indexed Urls","description":"inspected_urls - indexed_urls."},"indexed_pct_of_inspected":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Indexed Pct Of Inspected","description":"indexed_urls / inspected_urls as a percentage, null when nothing is inspected. This is NOT coverage of the submitted set - see inspected_urls."},"rich_results_pass":{"type":"integer","title":"Rich Results Pass","default":0},"rich_results_fail":{"type":"integer","title":"Rich Results Fail","default":0},"rich_results_unknown":{"type":"integer","title":"Rich Results Unknown","description":"Neither PASS nor FAIL: most commonly no structured data was attempted, so this is not a failure count.","default":0}},"type":"object","required":["inspected_urls","indexed_urls","not_indexed_urls"],"title":"CoverageSummary","description":"Indexed-vs-inspected counts derived from per-URL inspection verdicts.\n\nThe sitemap report's own indexed figure is frequently absent - Search Console does not\ncompute it for large properties - so ``index_coverage_pct`` on the sitemaps endpoint is\nnull and these per-URL verdicts are the only way to answer \"how many of my pages are\nindexed\". Note the denominator: ``inspected_urls`` is the number of URLs with a verdict,\nwhich on a large property is a fraction of the URLs submitted in sitemaps. Answer the\nquestion against ``inspected_urls`` and say so, rather than implying coverage of the\nwhole submitted set - divide ``inspected_urls`` by the sitemaps endpoint's\n``total_submitted`` to state that fraction explicitly, since it is the single most\nimportant qualifier on ``indexed_pct_of_inspected``.\n\nThat inspected slice is also not a random sample of the site. The collector\n(``GetSitemapURLsForProperty`` in ``backend/services/indexing-collector``) selects URLs\nwith ``ORDER BY last_inspected_at ASC NULLS FIRST, url ASC``, so on a property still\npartway through its first inspection cycle the inspected set is effectively \"whichever\nURLs sort earliest alphabetically\", not a cross-section of the site - do not extrapolate\n``indexed_pct_of_inspected`` to the uninspected remainder. GET .../indexing/diagnostics'\n``analysis.avg_inspections_per_day`` reports the current inspection pace, from which a\ncaller can derive whether coverage is still growing and roughly how long the remainder\nwould take at that pace."},"CrawlerAccessPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CrawlerCategoryPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"version_id":{"type":"string","format":"uuid","title":"Version Id"},"fetched_at":{"type":"string","format":"date-time","title":"Fetched At"},"path_checked":{"type":"string","title":"Path Checked"},"bypass_count":{"type":"integer","title":"Bypass Count","default":0}},"type":"object","required":["items","domain_id","version_id","fetched_at","path_checked"],"title":"CrawlerAccessPublic"},"CrawlerCategoryPublic":{"properties":{"category":{"type":"string","title":"Category","description":"'search_engine', 'ai_crawler', or 'other'."},"crawlers":{"items":{"$ref":"#/components/schemas/CrawlerStatusPublic"},"type":"array","title":"Crawlers"}},"type":"object","required":["category","crawlers"],"title":"CrawlerCategoryPublic"},"CrawlerStatusPublic":{"properties":{"name":{"type":"string","title":"Name"},"allowed":{"type":"boolean","title":"Allowed"},"matched_rule":{"type":"string","title":"Matched Rule"},"has_specific_group":{"type":"boolean","title":"Has Specific Group","default":false},"bypasses_wildcard":{"type":"boolean","title":"Bypasses Wildcard","default":false},"crawl_delay_declared":{"type":"string","title":"Crawl Delay Declared","default":""},"honors_crawl_delay":{"type":"boolean","title":"Honors Crawl Delay","default":true}},"type":"object","required":["name","allowed","matched_rule"],"title":"CrawlerStatusPublic"},"CredentialInfo":{"properties":{"kind":{"type":"string","title":"Kind","description":"'api_key' or 'oauth'"},"credential_id":{"type":"string","format":"uuid","title":"Credential Id"},"user_id":{"type":"string","format":"uuid","title":"User Id"},"workspace_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Workspace Ids"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes granted to this credential, with aggregates already expanded. This is what the credential asked for, not necessarily what it can do right now - see effective_scopes."},"effective_scopes":{"items":{"type":"string"},"type":"array","title":"Effective Scopes","description":"Scopes intersected with the creator's CURRENT workspace role. This is the authoritative set: an operation outside it will fail. A client building a tool list must use this, not 'scopes', or it will advertise operations that always 403."},"roles":{"additionalProperties":true,"type":"object","title":"Roles","description":"Workspace id to the credential creator's current role in it."}},"type":"object","required":["kind","credential_id","user_id","workspace_ids","scopes","effective_scopes"],"title":"CredentialInfo","description":"Who the caller is, as ``GET /me`` reports it."},"CreditBalance":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id","description":"The workspace charged for requests made with this credential. For an API key that is the key's own workspace; for an OAuth grant covering several workspaces it is one fixed workspace out of the grant, so the bill does not depend on which resource a request happened to name."},"granted":{"type":"integer","title":"Granted","description":"Allowance for one billing cycle, from the plan."},"used":{"type":"integer","title":"Used","description":"Credits spent so far in the current cycle."},"remaining":{"type":"integer","title":"Remaining","description":"granted minus used, floored at 0. A concurrent burst can overshoot the allowance slightly, which is reported as 0 rather than as a negative."},"cycle_start":{"type":"string","format":"date","title":"Cycle Start"},"cycle_end":{"type":"string","format":"date","title":"Cycle End","description":"Last day of the current cycle, inclusive. The allowance refills the following day, on the workspace's billing anniversary, and does not roll over."},"member":{"$ref":"#/components/schemas/MemberCredits"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether credit charging is live. False means the numbers above are being tracked but nothing is being enforced, so a request that would exhaust the allowance still succeeds."}},"type":"object","required":["workspace_id","granted","used","remaining","cycle_start","cycle_end","member","enabled"],"title":"CreditBalance","description":"What ``GET /credits`` reports for the workspace that pays for this credential."},"CreditLogEntry":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"method":{"type":"string","title":"Method"},"route_template":{"type":"string","title":"Route Template","description":"The route as declared, never the resolved path: '/domains/{domain_id}/rum/vitals', not the path with the id filled in."},"status_code":{"type":"integer","title":"Status Code"},"credits_charged":{"type":"integer","title":"Credits Charged","description":"What this request cost. 0 for a discovery route, for an answer served from cache, and for any response that was not 2xx."},"member_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Member User Id","description":"The workspace member behind the credential that made the request, which is who a per-member cap applies to. Null when that account has since been erased."}},"type":"object","required":["timestamp","method","route_template","status_code","credits_charged"],"title":"CreditLogEntry","description":"One public API request, and what it cost."},"CruxComparePeriod":{"properties":{"period_name":{"type":"string","title":"Period Name"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"metrics":{"items":{"$ref":"#/components/schemas/CruxMetricPoint"},"type":"array","title":"Metrics"},"average_p75":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average P75","description":"Mean of the 75th-percentile value across all metrics in this period."}},"type":"object","required":["period_name","start_date","end_date","metrics"],"title":"CruxComparePeriod"},"CruxCompareResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"form_factor":{"type":"string","title":"Form Factor"},"periods":{"items":{"$ref":"#/components/schemas/CruxComparePeriod"},"type":"array","title":"Periods","description":"Always exactly two entries."},"metric_changes":{"additionalProperties":{"type":"number"},"type":"object","title":"Metric Changes","description":"metric_name to percent change in 75th-percentile value, period 1 to period 2."}},"type":"object","required":["domain_id","form_factor","periods","metric_changes"],"title":"CruxCompareResponse"},"CruxComparisonPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsComparisonPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"CruxComparisonPublic"},"CruxCoreMetricsPublic":{"properties":{"lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Ms"},"cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls","description":"Cumulative Layout Shift score (unitless)."},"inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Ms"},"ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Ms"}},"type":"object","title":"CruxCoreMetricsPublic"},"CruxHistogramBin":{"properties":{"start":{"type":"number","title":"Start"},"density":{"type":"number","title":"Density","description":"Fraction of measurements in this bin, 0-1."}},"type":"object","required":["start","density"],"title":"CruxHistogramBin"},"CruxHistogramResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"metric_name":{"type":"string","title":"Metric Name"},"form_factor":{"type":"string","title":"Form Factor"},"collection_date":{"type":"string","format":"date","title":"Collection Date"},"histogram":{"items":{"$ref":"#/components/schemas/CruxHistogramBin"},"type":"array","title":"Histogram"}},"type":"object","required":["domain_id","metric_name","form_factor","collection_date","histogram"],"title":"CruxHistogramResponse"},"CruxMetricPoint":{"properties":{"collection_date":{"type":"string","format":"date","title":"Collection Date"},"form_factor":{"type":"string","title":"Form Factor","description":"'PHONE', 'DESKTOP', or 'ALL'"},"metric_name":{"type":"string","title":"Metric Name"},"p50_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Value"},"p75_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Value"},"p90_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Value"},"p95_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Value"},"p99_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P99 Value"},"good_percentage":{"type":"number","title":"Good Percentage"},"needs_improvement_percentage":{"type":"number","title":"Needs Improvement Percentage"},"poor_percentage":{"type":"number","title":"Poor Percentage"}},"type":"object","required":["collection_date","form_factor","metric_name","good_percentage","needs_improvement_percentage","poor_percentage"],"title":"CruxMetricPoint","description":"One Core Web Vitals data point for one metric, form factor, and collection date.\n\n``p*_value`` units depend on metric_name: milliseconds for largest_contentful_paint,\nfirst_contentful_paint, interaction_to_next_paint, and\nexperimental_time_to_first_byte; a unitless ratio (0-1) for cumulative_layout_shift.\nThe ``*_percentage`` fields are CrUX's own good/needs-improvement/poor buckets and\nalways sum to ~100."},"CruxMetricsComparisonPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/CruxCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/CruxCoreMetricsPublic"},{"type":"null"}]}},"type":"object","title":"CruxMetricsComparisonPublic"},"CruxMetricsPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/CruxCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/CruxCoreMetricsPublic"},{"type":"null"}]},"comparison":{"anyOf":[{"$ref":"#/components/schemas/CruxMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"CruxMetricsPublic"},"CruxOverviewPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsPublic"},{"type":"null"}]}},"type":"object","title":"CruxOverviewPublic","description":"Chrome UX Report field data for the origin, as Google publishes it."},"CruxOverviewResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"latest_collection_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Latest Collection Date","description":"Null when no CrUX data has been collected yet."},"core_web_vitals":{"additionalProperties":{"additionalProperties":true,"type":"object"},"type":"object","title":"Core Web Vitals","description":"metric_name to form_factor to that metric's latest values and rating."},"overall_assessment":{"type":"string","title":"Overall Assessment","description":"'good', 'needs_improvement', 'poor', or 'no_data'"},"data_availability":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Data Availability","description":"form_factor to whether it has data."}},"type":"object","required":["domain_id","core_web_vitals","overall_assessment","data_availability"],"title":"CruxOverviewResponse"},"CruxTimelineResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CruxMetricPoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"form_factor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Form Factor"},"metric_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Metric Name","description":"The metric_name filter applied, or null when every metric is included."},"metric_names_available":{"items":{"type":"string"},"type":"array","title":"Metric Names Available","description":"Every metric_name present in this window before any metric_name filter, so a caller can see what else is here and what to narrow to. CrUX carries far more than the five headline vitals - LCP sub-parts, resource types and form-factor shares all arrive as their own metric_name - so an unfiltered timeline spends its size budget across all of them and can return only a couple of collection dates per metric, which is not a trend."},"total_periods":{"type":"integer","title":"Total Periods","description":"Number of distinct weekly collection dates covered."}},"type":"object","required":["items","domain_id","start_date","end_date","total_periods"],"title":"CruxTimelineResponse"},"CruxTrendResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CruxMetricPoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"metric_name":{"type":"string","title":"Metric Name"},"form_factor":{"type":"string","title":"Form Factor"},"start_date":{"type":"string","format":"date","title":"Start Date"},"end_date":{"type":"string","format":"date","title":"End Date"},"trend_direction":{"type":"string","title":"Trend Direction","description":"'improving', 'declining', or 'stable'"},"trend_percentage":{"type":"number","title":"Trend Percentage","description":"Percent change in the 75th-percentile value from the first period to the last."}},"type":"object","required":["items","domain_id","metric_name","form_factor","start_date","end_date","trend_direction","trend_percentage"],"title":"CruxTrendResponse"},"CruxUrlBulkAddError":{"properties":{"url":{"type":"string","title":"Url"},"error":{"type":"string","title":"Error"}},"type":"object","required":["url","error"],"title":"CruxUrlBulkAddError"},"CruxUrlBulkAddRequest":{"properties":{"urls":{"items":{"type":"string"},"type":"array","title":"Urls","description":"Full URLs to monitor, max 200 per request."}},"type":"object","required":["urls"],"title":"CruxUrlBulkAddRequest"},"CruxUrlBulkAddResponse":{"properties":{"created":{"items":{"$ref":"#/components/schemas/CruxUrlPublic"},"type":"array","title":"Created"},"errors":{"items":{"$ref":"#/components/schemas/CruxUrlBulkAddError"},"type":"array","title":"Errors","description":"Per-URL failures: invalid format, already monitored, or plan limit reached."}},"type":"object","required":["created","errors"],"title":"CruxUrlBulkAddResponse"},"CruxUrlBulkDeleteError":{"properties":{"url_id":{"type":"string","title":"Url Id"},"error":{"type":"string","title":"Error"}},"type":"object","required":["url_id","error"],"title":"CruxUrlBulkDeleteError"},"CruxUrlBulkDeleteRequest":{"properties":{"url_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Url Ids","description":"URL ids to delete, max 200 per request."}},"type":"object","required":["url_ids"],"title":"CruxUrlBulkDeleteRequest"},"CruxUrlBulkDeleteResponse":{"properties":{"deleted":{"type":"integer","title":"Deleted"},"errors":{"items":{"$ref":"#/components/schemas/CruxUrlBulkDeleteError"},"type":"array","title":"Errors"}},"type":"object","required":["deleted","errors"],"title":"CruxUrlBulkDeleteResponse"},"CruxUrlPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"url":{"type":"string","title":"Url"},"is_origin":{"type":"boolean","title":"Is Origin","description":"True for the domain root, which cannot be removed."},"enabled":{"type":"boolean","title":"Enabled"},"last_collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Collected At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","url","is_origin","enabled","created_at","updated_at"],"title":"CruxUrlPublic"},"DailyInspectionCount":{"properties":{"date":{"type":"string","format":"date","title":"Date"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["date","count"],"title":"DailyInspectionCount"},"DefaultConfigPublic":{"properties":{"sections_config":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Sections Config"}},"type":"object","required":["sections_config"],"title":"DefaultConfigPublic","description":"A ready-to-use sections_config with every section and its default widgets enabled."},"DeletedResponse":{"properties":{"deleted":{"type":"boolean","title":"Deleted","default":true},"id":{"type":"string","format":"uuid","title":"Id"}},"type":"object","required":["id"],"title":"DeletedResponse","description":"Uniform body for a successful delete."},"DeviceCategoryMetrics":{"properties":{"name":{"type":"string","title":"Name"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["name"],"title":"DeviceCategoryMetrics"},"DeviceVitalsComparisonPublic":{"properties":{"lcp_ms":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]},"cls":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]},"inp_ms":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]},"ttfb_ms":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]}},"type":"object","title":"DeviceVitalsComparisonPublic","description":"Movement of each Core Web Vital for one device class."},"DeviceVitalsPublic":{"properties":{"lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Ms","description":"Largest Contentful Paint."},"cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls","description":"Cumulative Layout Shift score (unitless)."},"inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Ms","description":"Interaction to Next Paint."},"ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Ms","description":"Time to First Byte."},"lcp_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lcp Samples","description":"Page loads behind lcp_ms. Not shared with the other metrics."},"cls_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cls Samples","description":"Page loads behind cls. Not shared with the other metrics."},"inp_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inp Samples","description":"Page loads behind inp_ms. Not shared with the other metrics."},"ttfb_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttfb Samples","description":"Page loads behind ttfb_ms. Not shared with the other metrics."}},"type":"object","title":"DeviceVitalsPublic","description":"75th-percentile Core Web Vitals for one device class.\n\nEach metric is a percentile over the page loads that reported THAT metric, and those\npopulations are not the same set of loads. A slow load reports TTFB and can be abandoned\nbefore LCP is ever finalised, so TTFB carries a slow tail that LCP never sees, and\n``ttfb_ms`` above ``lcp_ms`` is ordinary rather than a contradiction - even though TTFB\nis a component of LCP within any single load. Read the ``*_samples`` denominators before\ncomparing two metrics here against each other. They are populated for RUM only; CrUX\npublishes no per-metric sample count, so they stay null on a CrUX block."},"DevicesPublic":{"properties":{"by_device_type":{"items":{"$ref":"#/components/schemas/DeviceCategoryMetrics"},"type":"array","title":"By Device Type"},"by_browser":{"items":{"$ref":"#/components/schemas/DeviceCategoryMetrics"},"type":"array","title":"By Browser"},"by_os":{"items":{"$ref":"#/components/schemas/DeviceCategoryMetrics"},"type":"array","title":"By Os"},"browser_versions":{"anyOf":[{"items":{"$ref":"#/components/schemas/BrowserVersionMetrics"},"type":"array"},{"type":"null"}],"title":"Browser Versions","description":"Present only when the request set include_versions=true."},"os_versions":{"anyOf":[{"items":{"$ref":"#/components/schemas/OSVersionMetrics"},"type":"array"},{"type":"null"}],"title":"Os Versions"},"screen_sizes":{"anyOf":[{"items":{"$ref":"#/components/schemas/ScreenSizeMetrics"},"type":"array"},{"type":"null"}],"title":"Screen Sizes"},"total_visitors":{"type":"integer","title":"Total Visitors","default":0},"total_visitors_events":{"type":"integer","title":"Total Visitors Events","default":0},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that recorded a session (YYYY-MM-DD), or null when the window holds none. The same value GET /analytics/overview reports for the identical window, so a caller chaining one into the other is not told about a truncated window once and left to assume the second answer is complete. Check it before quoting a ranking as the window's ranking."}},"type":"object","title":"DevicesPublic"},"DiagnosticsAnalysis":{"properties":{"days_since_first_inspection":{"type":"integer","title":"Days Since First Inspection"},"expected_unique_urls":{"type":"integer","title":"Expected Unique Urls","description":"days_since_first_inspection * daily budget, capped at the sitemap URL count."},"actual_unique_urls":{"type":"integer","title":"Actual Unique Urls"},"inspection_gap":{"type":"integer","title":"Inspection Gap","description":"max(0, expected_unique_urls - actual_unique_urls)."},"days_with_inspections":{"type":"integer","title":"Days With Inspections"},"avg_inspections_per_day":{"type":"number","title":"Avg Inspections Per Day"}},"type":"object","required":["days_since_first_inspection","expected_unique_urls","actual_unique_urls","inspection_gap","days_with_inspections","avg_inspections_per_day"],"title":"DiagnosticsAnalysis"},"DistributionTimelineItemPublic":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"good_pct":{"type":"number","title":"Good Pct"},"needs_improvement_pct":{"type":"number","title":"Needs Improvement Pct"},"poor_pct":{"type":"number","title":"Poor Pct"},"total_count":{"type":"integer","title":"Total Count"}},"type":"object","required":["timestamp","good_pct","needs_improvement_pct","poor_pct","total_count"],"title":"DistributionTimelineItemPublic"},"DistributionTimelinePublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DistributionTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date","description":"Same value as window_start below - kept for backward compatibility."},"end_date":{"type":"string","format":"date-time","title":"End Date","description":"Same value as window_end below - kept for backward compatibility."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","domain_id","start_date","end_date","window_start","window_end"],"title":"DistributionTimelinePublicResponse"},"DomainEmailSubscriptionPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"domain_name":{"type":"string","title":"Domain Name"},"weekly_summary_enabled":{"type":"boolean","title":"Weekly Summary Enabled","default":true}},"type":"object","required":["domain_id","domain_name"],"title":"DomainEmailSubscriptionPublic"},"DomainEmailSubscriptionUpdatePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"weekly_summary_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Weekly Summary Enabled"}},"type":"object","required":["domain_id"],"title":"DomainEmailSubscriptionUpdatePublic"},"DomainExpirationPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"checked_at":{"type":"string","format":"date-time","title":"Checked At"},"domain_name":{"type":"string","title":"Domain Name"},"tld":{"type":"string","title":"Tld"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"registrar":{"type":"string","title":"Registrar","default":""},"whois_server":{"type":"string","title":"Whois Server","default":""},"days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiry"},"check_supported":{"type":"boolean","title":"Check Supported","description":"Some TLDs' registries do not expose WHOIS expiration data."},"error_message":{"type":"string","title":"Error Message","default":""}},"type":"object","required":["domain_id","checked_at","domain_name","tld","check_supported"],"title":"DomainExpirationPublic","description":"WHOIS-derived domain registration expiration, distinct from certificate expiry."},"DomainHealthSnapshot":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"configured_modules":{"items":{"type":"string"},"type":"array","title":"Configured Modules","description":"Modules that hold configuration for this domain, the same list GET /domains returns as active_modules. A module named here is never a bare null block: it is either populated, or an empty-but-present shell (e.g. {mobile: null, desktop: null} for crux) meaning it is connected but reported nothing in this window - not that it is absent. rum and analytics are never named here even in active use, since neither has a configuration row to test; their null always means 'no data in this window', never 'definitely not configured'. Without configured_modules, 'no Google Analytics on this domain' and 'Google Analytics has no data in the last 28 days' were the same null."},"rum":{"anyOf":[{"$ref":"#/components/schemas/RumMetricsPublic"},{"type":"null"}]},"crux":{"anyOf":[{"$ref":"#/components/schemas/CruxMetricsPublic"},{"type":"null"}]},"synthetic":{"anyOf":[{"$ref":"#/components/schemas/SyntheticMetricsPublic"},{"type":"null"}]},"indexing":{"anyOf":[{"$ref":"#/components/schemas/IndexingStatsPublic"},{"type":"null"}]},"robots":{"anyOf":[{"$ref":"#/components/schemas/RobotsStatusPublic"},{"type":"null"}]},"analytics":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsMetricsPublic"},{"type":"null"}]},"uptime":{"anyOf":[{"$ref":"#/components/schemas/UptimeMetricsPublic"},{"type":"null"}]},"gsc":{"anyOf":[{"$ref":"#/components/schemas/GSCMetricsPublic"},{"type":"null"}]},"ga":{"anyOf":[{"$ref":"#/components/schemas/GAMetricsPublic"},{"type":"null"}]},"compare_range_start":{"type":"string","format":"date-time","title":"Compare Range Start","description":"Start of the window every '.comparison' block above is measured against - fixed at 28 days before meta.range_start, not configurable. A calendar-month comparison (e.g. 'has uptime got worse than last month') can legitimately disagree with this trailing window; for that specific question use GET /uptime/availability?compare_previous=true&compare_mode=calendar_month instead."},"compare_range_end":{"type":"string","format":"date-time","title":"Compare Range End","description":"End of the comparison window (exclusive). Always equal to meta.range_start."},"meta":{"$ref":"#/components/schemas/ResponseMeta"}},"type":"object","required":["domain_id","compare_range_start","compare_range_end","meta"],"title":"DomainHealthSnapshot","description":"Cross-module snapshot: one block per module with data, null otherwise."},"DomainLimitsPublic":{"properties":{"crux_url_count":{"type":"integer","title":"Crux Url Count","description":"CrUX URLs monitored across every domain in the workspace."},"crux_url_limit":{"type":"integer","title":"Crux Url Limit"},"synthetic_url_count":{"type":"integer","title":"Synthetic Url Count","description":"Synthetic URLs monitored across every domain in the workspace."},"synthetic_url_limit":{"type":"integer","title":"Synthetic Url Limit"},"allowed_uptime_intervals_seconds":{"items":{"type":"integer"},"type":"array","title":"Allowed Uptime Intervals Seconds","description":"Uptime check intervals the plan permits; any other value is rejected."},"allowed_synthetic_intervals_seconds":{"items":{"type":"integer"},"type":"array","title":"Allowed Synthetic Intervals Seconds","description":"Synthetic test intervals the plan permits, in seconds - 3600 is the '1h' frequency the synthetic endpoints take."},"rum_page_view_count":{"type":"integer","title":"Rum Page View Count","description":"RUM page views recorded so far in the workspace's current billing cycle. The cycle is anchored to the billing day, not the calendar month."},"rum_page_view_limit":{"type":"integer","title":"Rum Page View Limit"},"analytics_events_count":{"type":"integer","title":"Analytics Events Count","description":"Web analytics events recorded in the workspace's current billing cycle."},"analytics_events_limit":{"type":"integer","title":"Analytics Events Limit"}},"type":"object","required":["crux_url_count","crux_url_limit","synthetic_url_count","synthetic_url_limit","allowed_uptime_intervals_seconds","allowed_synthetic_intervals_seconds","rum_page_view_count","rum_page_view_limit","analytics_events_count","analytics_events_limit"],"title":"DomainLimitsPublic","description":"Plan allowances and current usage for the workspace a domain belongs to.\n\nEvery count is workspace-wide rather than per-domain: the CrUX and Synthetic URL caps\nare pooled across all of a workspace's domains, and the two monthly meters bill the\nworkspace. Two domains in the same workspace therefore return identical bodies."},"DomainOverviewPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"domain":{"type":"string","title":"Domain"},"configured_modules":{"items":{"type":"string"},"type":"array","title":"Configured Modules","description":"Modules that hold configuration for this domain, the same list GET /health's configured_modules and GET /domains's active_modules return. A null block for a module named here means it is configured but reported nothing for this domain in this window - not that it is absent. rum and analytics are never named here even when in active use: neither has a domain-scoped configuration row to test, so their null always means 'no data in this window', never 'definitely not configured'."},"rum":{"anyOf":[{"$ref":"#/components/schemas/RumOverviewPublic"},{"type":"null"}]},"crux":{"anyOf":[{"$ref":"#/components/schemas/CruxOverviewPublic"},{"type":"null"}]},"synthetic":{"anyOf":[{"$ref":"#/components/schemas/SyntheticOverviewPublic"},{"type":"null"}]},"uptime":{"anyOf":[{"$ref":"#/components/schemas/UptimeOverviewPublic"},{"type":"null"}]},"robots":{"anyOf":[{"$ref":"#/components/schemas/RobotsOverviewPublic"},{"type":"null"}]},"indexing":{"anyOf":[{"$ref":"#/components/schemas/IndexingOverviewPublic"},{"type":"null"}]},"gsc":{"anyOf":[{"$ref":"#/components/schemas/GSCOverviewPublic"},{"type":"null"}]},"ga":{"anyOf":[{"$ref":"#/components/schemas/GAOverviewPublic"},{"type":"null"}]},"analytics":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsOverviewPublic"},{"type":"null"}]},"comparison":{"$ref":"#/components/schemas/OverviewComparisonPublic"},"compare_range_start":{"type":"string","format":"date-time","title":"Compare Range Start","description":"Start of the window every 'comparison' block above is measured against - fixed at 28 days before meta.range_start, matching GET /health's own compare_range_start/compare_range_end exactly, not independently computed."},"compare_range_end":{"type":"string","format":"date-time","title":"Compare Range End","description":"End of the comparison window (exclusive). Always equal to meta.range_start."},"unavailable_modules":{"items":{"type":"string"},"type":"array","title":"Unavailable Modules","description":"Modules whose block is null because their query failed, not because they have no data. Any module not listed here is genuinely empty or unconfigured for this domain."},"meta":{"$ref":"#/components/schemas/ResponseMeta"}},"type":"object","required":["domain_id","domain","compare_range_start","compare_range_end","meta"],"title":"DomainOverviewPublic","description":"Cross-module summary for one domain: one block per module, null when empty."},"DomainSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"domain":{"type":"string","title":"Domain"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"active_modules":{"items":{"type":"string"},"type":"array","title":"Active Modules","description":"Modules that hold configuration for this domain, from: uptime, crux, synthetic, gsc, ga, indexing, robots, sitemap, alerts, reports. Reports *configured*, not *has data*. 'analytics' and 'rum' are never listed and their absence carries no meaning: neither has a domain-scoped configuration row to test, because both activate when the tracking script starts reporting. Call those endpoints directly, or GET /rum/has-data, rather than inferring from this list."}},"type":"object","required":["id","workspace_id","domain"],"title":"DomainSummary"},"EcommerceOverviewPublic":{"properties":{"revenue":{"type":"number","title":"Revenue","description":"In the currency reported by the 'currency' field, not necessarily the one configured for the domain - read 'currency_note' when they differ.","default":0.0},"currency":{"type":"string","title":"Currency","description":"Currency the revenue figures are denominated in, taken from this domain's own first-party analytics configuration (GET .../analytics/config). This is NOT the currency of any linked Google Analytics property: the two are separate settings and really do disagree in practice - a domain configured USD here while its GA4 property reports EUR is a configuration mismatch to fix in the dashboard, not a disagreement between these tools. Compare against GET .../ga/connection's currency before quoting either as 'the' store currency.","default":""},"currency_note":{"type":"string","title":"Currency Note","description":"Set when no purchase in the window uses the domain's configured currency and the figures fall back to the one carrying the most orders. A configured currency that matches nothing used to filter every order out, reporting revenue 0 next to a non-zero purchase count.","default":""},"orders":{"type":"integer","title":"Orders","default":0},"average_order_value":{"type":"number","title":"Average Order Value","default":0.0},"conversion_rate":{"type":"number","title":"Conversion Rate","description":"Orders divided by sessions, as a percentage. Timeline points use the same definition. The divisor is in 'sessions' below, so this number can be checked rather than inferred.","default":0.0},"sessions":{"type":"integer","title":"Sessions","description":"The conversion-rate divisor: sessions whose start timestamp falls inside the requested window, counted from the sessions table with duplicate session rows collapsed (ClickHouse FINAL) over the window's exact timestamps. GET /analytics/overview counts sessions the same way ONLY when it serves the raw query path; for a request with no filters it is normally served from a day-keyed pre-aggregated rollup instead, whose window is floored to the start of the first day and extended to the end of the last. A period_days request made at 14:00 therefore covers ~31 whole calendar days there against exactly 30x24h here, so the overview's session count is the LARGER of the two - by roughly one day of traffic, not by a fraction of a per cent. To compare the two figures (or to reconcile conversion_rate against the overview), request both with explicit start_date AND end_date: both windows are then already day-aligned and the counts agree, apart from up to 20 minutes of the newest sessions when the window includes today, which is how often the rollup refreshes.","default":0},"revenue_prev":{"type":"number","title":"Revenue Prev","default":0.0},"orders_prev":{"type":"integer","title":"Orders Prev","default":0},"revenue_change":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Change","description":"revenue vs. the preceding window of equal length, as a relative percent change (e.g. 12.5 for +12.5%). NULL when revenue_prev is 0: a percent change against a zero baseline is undefined, and this field used to report a hardcoded 100.0 there - a store's first window with any revenue at all read as '+100%' rather than 'no baseline to compare against'. Same rule as OverviewPublic.visitors_change_pct."},"orders_change":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Orders Change","description":"Same convention as revenue_change, for orders - null when orders_prev is 0."},"product_views":{"type":"integer","title":"Product Views","default":0},"add_to_carts":{"type":"integer","title":"Add To Carts","default":0},"checkouts":{"type":"integer","title":"Checkouts","default":0},"purchases":{"type":"integer","title":"Purchases","default":0},"items_sold":{"type":"integer","title":"Items Sold","default":0},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day in the requested window with any ecommerce activity at all - a product view, an add-to-cart, a checkout or a purchase (YYYY-MM-DD). Null means the window holds none. Check it against the window you asked for before reporting a zero: without it, a month that genuinely made no sales and a month whose data has not been ingested yet are the same response. Derived from the newest active point in 'timeline', so it inherits that list's bucket size: exact for the default granularity=day (and for granularity=hour, whose bucket still names a single calendar day) but for granularity=week this is the START of the newest active week, not the day activity happened within it - it can understate the true newest active day by up to 6 days. Request granularity=day when this field needs to be exact."},"timeline":{"items":{"$ref":"#/components/schemas/EcommerceTimelinePoint"},"type":"array","title":"Timeline"},"top_products":{"items":{"$ref":"#/components/schemas/EcommerceProductMetrics"},"type":"array","title":"Top Products"}},"type":"object","title":"EcommerceOverviewPublic"},"EcommerceProductMetrics":{"properties":{"product_id":{"type":"string","title":"Product Id"},"name":{"type":"string","title":"Name"},"views":{"type":"integer","title":"Views","default":0},"add_to_carts":{"type":"integer","title":"Add To Carts","default":0},"purchases":{"type":"integer","title":"Purchases","default":0},"revenue":{"type":"number","title":"Revenue","description":"In the domain's configured currency; see GET .../config.","default":0.0},"conversion_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Rate","description":"purchases / views as a percentage, null when the product has no recorded views. Many sites tag product_view with a catalogue id and purchase with a SKU, so a product with real revenue routinely has zero views; null says the two cannot be joined, where 0.0 would claim the product never converts."}},"type":"object","required":["product_id","name"],"title":"EcommerceProductMetrics"},"EcommerceProductsPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EcommerceProductMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"EcommerceProductsPublic"},"EcommerceTimelinePoint":{"properties":{"date":{"type":"string","title":"Date"},"revenue":{"type":"number","title":"Revenue","default":0.0},"orders":{"type":"integer","title":"Orders","default":0},"average_order_value":{"type":"number","title":"Average Order Value","default":0.0},"conversion_rate":{"type":"number","title":"Conversion Rate","default":0.0},"product_views":{"type":"integer","title":"Product Views","default":0},"add_to_carts":{"type":"integer","title":"Add To Carts","default":0},"checkouts":{"type":"integer","title":"Checkouts","default":0},"purchases":{"type":"integer","title":"Purchases","default":0}},"type":"object","required":["date"],"title":"EcommerceTimelinePoint"},"EngagementCorrelationBinPublic":{"properties":{"bin_start":{"type":"number","title":"Bin Start"},"bin_end":{"type":"number","title":"Bin End"},"bin_label":{"type":"string","title":"Bin Label"},"page_views":{"type":"integer","title":"Page Views"},"engagement_value":{"type":"number","title":"Engagement Value"},"status":{"type":"string","title":"Status","description":"'good', 'needs-improvement', or 'poor'."}},"type":"object","required":["bin_start","bin_end","bin_label","page_views","engagement_value","status"],"title":"EngagementCorrelationBinPublic"},"EngagementCorrelationPublicResponse":{"properties":{"histogram":{"items":{"$ref":"#/components/schemas/EngagementCorrelationBinPublic"},"type":"array","title":"Histogram"},"performance_metric":{"type":"string","title":"Performance Metric"},"engagement_metric":{"type":"string","title":"Engagement Metric"},"device_type":{"type":"string","title":"Device Type"},"good_threshold":{"type":"number","title":"Good Threshold"},"poor_threshold":{"type":"number","title":"Poor Threshold"},"unit":{"type":"string","title":"Unit"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["histogram","performance_metric","engagement_metric","device_type","good_threshold","poor_threshold","unit","window_start","window_end"],"title":"EngagementCorrelationPublicResponse"},"EngagementMetricPublic":{"properties":{"metric_type":{"type":"string","title":"Metric Type"},"total_count":{"type":"integer","title":"Total Count"},"unique_sessions":{"type":"integer","title":"Unique Sessions"}},"type":"object","required":["metric_type","total_count","unique_sessions"],"title":"EngagementMetricPublic"},"EngagementPublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EngagementMetricPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date","description":"Same value as window_start below - kept for backward compatibility."},"end_date":{"type":"string","format":"date-time","title":"End Date","description":"Same value as window_end below - kept for backward compatibility."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"total_interactions":{"type":"integer","title":"Total Interactions"},"scroll_depth_50_pct":{"type":"number","title":"Scroll Depth 50 Pct","description":"Percentage of measured pageviews that scrolled at least half the page.","default":0.0},"scroll_depth_90_pct":{"type":"number","title":"Scroll Depth 90 Pct","description":"Percentage of measured pageviews that scrolled at least 90 percent.","default":0.0},"pageviews_with_scroll_data":{"type":"integer","title":"Pageviews With Scroll Data","description":"Pageviews the two percentages are computed over.","default":0}},"type":"object","required":["items","domain_id","start_date","end_date","window_start","window_end","total_interactions"],"title":"EngagementPublicResponse"},"ErrorDetailPublic":{"properties":{"error_id":{"type":"string","title":"Error Id"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"error_type":{"type":"string","title":"Error Type"},"message":{"type":"string","title":"Message"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"lineno":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lineno"},"colno":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Colno"},"stack":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stack"},"url":{"type":"string","title":"Url"},"device_type":{"type":"string","title":"Device Type"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id","description":"Always omitted. No scope makes individual session identity readable."},"browser":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Browser"},"browser_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Browser Version"},"os":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os"},"os_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Os Version"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer"},"resource_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resource Url"},"element_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Element Id"},"element_class":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Element Class"},"tag_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag Name"}},"type":"object","required":["error_id","timestamp","error_type","message","url","device_type"],"title":"ErrorDetailPublic","description":"One error occurrence, harvested from the customer's own monitored site.\n\n``message``, ``stack``, ``filename``, ``element_id``, and ``element_class`` are\nattacker-controlled free text: email addresses and long opaque tokens are replaced\nwith '[redacted]'. ``url``, ``referrer``, and ``resource_url`` have their query\nstring stripped. ``session_id`` is omitted. None of this is relaxable \u2013 it applies to\nevery credential, at every scope."},"ErrorDetailsPublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ErrorDetailPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"total_errors":{"type":"integer","title":"Total Errors"},"total_sessions":{"type":"integer","title":"Total Sessions"},"affected_sessions":{"type":"integer","title":"Affected Sessions"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","total_errors","total_sessions","affected_sessions","window_start","window_end"],"title":"ErrorDetailsPublicResponse","description":"Cursor-paginated error detail list.\n\nAlso carries the aggregate counts for the whole window, not just the returned page."},"ErrorGroupPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ErrorGroupPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"ErrorGroupPageResponse","description":"Cursor-paginated distinct-error-kind list, for GET /rum/errors/groups."},"ErrorGroupPublic":{"properties":{"error_type":{"type":"string","title":"Error Type"},"kind":{"type":"string","title":"Kind"},"occurrences":{"type":"integer","title":"Occurrences"},"affected_sessions":{"type":"integer","title":"Affected Sessions"},"last_seen":{"type":"string","format":"date-time","title":"Last Seen"},"sample_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sample Url","description":"Query string always stripped."}},"type":"object","required":["error_type","kind","occurrences","affected_sessions","last_seen"],"title":"ErrorGroupPublic","description":"A distinct error kind (the message up to the first colon) with aggregates."},"ErrorSummaryPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ErrorSummaryPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"ErrorSummaryPageResponse","description":"Cursor-paginated error-type summary list, for GET /rum/errors."},"ErrorSummaryPublic":{"properties":{"error_type":{"type":"string","title":"Error Type"},"device_type":{"type":"string","title":"Device Type"},"browser":{"type":"string","title":"Browser","description":"Browser name. One row exists per error type, device type and browser, so sum error_count across rows to total by any one of the three.","default":"Unknown"},"error_count":{"type":"integer","title":"Error Count"},"affected_sessions":{"type":"integer","title":"Affected Sessions"}},"type":"object","required":["error_type","device_type","error_count","affected_sessions"],"title":"ErrorSummaryPublic"},"EvaluationWindow":{"type":"string","enum":["last_1","last_3","last_5","last_10"],"title":"EvaluationWindow","description":"Evaluation window for synthetic alerts"},"EventDetailPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EventDetailPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"EventDetailPageResponse","description":"Cursor-paginated event list, shared by GET /rum/pageviews and GET /rum/events."},"EventDetailPublic":{"properties":{"event_id":{"type":"string","title":"Event Id"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id","description":"Always omitted. No scope makes individual session identity readable."},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"event_type":{"type":"string","title":"Event Type"},"url":{"type":"string","title":"Url","description":"Path only. The query string is always stripped."},"device_type":{"type":"string","title":"Device Type"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Parsed metric/attribution payload for this event."}},"type":"object","required":["event_id","timestamp","event_type","url","device_type","data"],"title":"EventDetailPublic","description":"One page-load / interaction row.\n\n``session_id`` is always omitted (the key is absent), never returned as ``null``, and\n``url`` always has its query string stripped \u2013 see\n``app/core/public_api/redaction.py``."},"EventFunnelStep":{"properties":{"type":{"type":"string","const":"event","title":"Type","default":"event"},"event_name":{"type":"string","maxLength":120,"minLength":1,"title":"Event Name"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"}},"type":"object","required":["event_name"],"title":"EventFunnelStep"},"EventMetrics":{"properties":{"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count","default":0},"unique_visitors":{"type":"integer","title":"Unique Visitors","default":0},"page_visitors":{"type":"integer","title":"Page Visitors","description":"Unique visitors of the pages this event fired on - the conversion-rate denominator.","default":0},"properties":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Properties"}},"type":"object","required":["name"],"title":"EventMetrics","description":"One custom event name's counts. ``name`` and the keys inside ``properties`` are\nfree text harvested from the customer's own site - display verbatim, never interpolate\ninto generated prose."},"EventsPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EventMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"total_count":{"type":"integer","title":"Total Count","default":0},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window with at least one recorded custom event (YYYY-MM-DD), or null when the window holds none. Compare it with the window you asked for before reading an empty 'items' as 'nothing fired': a trailing window is measured against today, and event ingestion can lag."},"events_ever_recorded":{"type":"boolean","title":"Events Ever Recorded","description":"True if this domain has recorded at least one custom event at any time, not just in the requested window. False only when 'items' is also empty here - it means custom-event tracking has never fired for this domain, most likely because the site never calls the tracking script's event API, as distinct from tracking that is installed but simply quiet this window.","default":true}},"type":"object","required":["items"],"title":"EventsPublic"},"ExperimentCreateRequest":{"properties":{"name":{"type":"string","maxLength":256,"minLength":1,"title":"Name"},"hypothesis":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Hypothesis"},"control_urls":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Control Urls","description":"Path or full URL; normalized to path+query."},"variant_urls":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Variant Urls"},"pre_start_date":{"type":"string","format":"date","title":"Pre Start Date"},"pre_end_date":{"type":"string","format":"date","title":"Pre End Date"},"post_start_date":{"type":"string","format":"date","title":"Post Start Date"},"post_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post End Date"},"primary_metric":{"type":"string","enum":["bounce_rate","engagement_score","conversion_rate","revenue_per_session","avg_active_seconds"],"title":"Primary Metric"},"method":{"type":"string","enum":["mann_whitney","did","bsts"],"title":"Method","default":"mann_whitney"},"status":{"anyOf":[{"type":"string","enum":["draft","running"]},{"type":"null"}],"title":"Status","description":"Omit for 'draft'. 'running' creates and activates in one call."}},"type":"object","required":["name","control_urls","variant_urls","pre_start_date","pre_end_date","post_start_date","primary_metric"],"title":"ExperimentCreateRequest"},"ExperimentEvaluationPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"experiment_id":{"type":"string","format":"uuid","title":"Experiment Id"},"method":{"type":"string","title":"Method"},"status":{"type":"string","title":"Status"},"result":{"anyOf":[{},{"type":"null"}],"title":"Result","description":"Effect size, significance, and method-specific detail. Shape varies by 'method'."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"evaluated_at":{"type":"string","format":"date-time","title":"Evaluated At"}},"type":"object","required":["id","experiment_id","method","status","evaluated_at"],"title":"ExperimentEvaluationPublic"},"ExperimentPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"name":{"type":"string","title":"Name"},"hypothesis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hypothesis"},"control_urls":{"items":{"type":"string"},"type":"array","title":"Control Urls"},"variant_urls":{"items":{"type":"string"},"type":"array","title":"Variant Urls"},"pre_start_date":{"type":"string","format":"date","title":"Pre Start Date"},"pre_end_date":{"type":"string","format":"date","title":"Pre End Date"},"post_start_date":{"type":"string","format":"date","title":"Post Start Date"},"post_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post End Date"},"primary_metric":{"type":"string","enum":["bounce_rate","engagement_score","conversion_rate","revenue_per_session","avg_active_seconds"],"title":"Primary Metric"},"method":{"type":"string","enum":["mann_whitney","did","bsts"],"title":"Method"},"status":{"type":"string","enum":["draft","running","concluded","abandoned"],"title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","name","control_urls","variant_urls","pre_start_date","pre_end_date","post_start_date","primary_metric","method","status","created_at","updated_at"],"title":"ExperimentPublic"},"ExperimentUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":256,"minLength":1},{"type":"null"}],"title":"Name"},"hypothesis":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Hypothesis"},"status":{"anyOf":[{"type":"string","enum":["draft","running","concluded","abandoned"]},{"type":"null"}],"title":"Status"},"post_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post End Date"},"control_urls":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1},{"type":"null"}],"title":"Control Urls"},"variant_urls":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1},{"type":"null"}],"title":"Variant Urls"},"pre_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Pre Start Date"},"pre_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Pre End Date"},"post_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post Start Date"},"primary_metric":{"anyOf":[{"type":"string","enum":["bounce_rate","engagement_score","conversion_rate","revenue_per_session","avg_active_seconds"]},{"type":"null"}],"title":"Primary Metric"},"method":{"anyOf":[{"type":"string","enum":["mann_whitney","did","bsts"]},{"type":"null"}],"title":"Method","description":"Structural fields (urls, dates, metric, method) are only editable while status is 'draft'."}},"type":"object","title":"ExperimentUpdateRequest"},"FormFactor":{"type":"string","enum":["PHONE","DESKTOP","TABLET","ALL"],"title":"FormFactor","description":"Device form factors"},"FunnelCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"steps":{"items":{"anyOf":[{"$ref":"#/components/schemas/PageviewFunnelStep"},{"$ref":"#/components/schemas/EventFunnelStep"}]},"type":"array","maxItems":5,"minItems":2,"title":"Steps","description":"2 to 5 ordered steps."}},"type":"object","required":["name","steps"],"title":"FunnelCreateRequest"},"FunnelPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"name":{"type":"string","title":"Name"},"steps":{"items":{"anyOf":[{"$ref":"#/components/schemas/PageviewFunnelStep"},{"$ref":"#/components/schemas/EventFunnelStep"}]},"type":"array","title":"Steps"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","name","steps","created_at","updated_at"],"title":"FunnelPublic"},"FunnelUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"steps":{"anyOf":[{"items":{"anyOf":[{"$ref":"#/components/schemas/PageviewFunnelStep"},{"$ref":"#/components/schemas/EventFunnelStep"}]},"type":"array","maxItems":5,"minItems":2},{"type":"null"}],"title":"Steps"}},"type":"object","title":"FunnelUpdateRequest"},"GAAccountPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"account_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Label"},"token_valid":{"type":"boolean","title":"Token Valid","description":"False when the stored refresh token is invalid or expired \u2013 collection for properties under this account has stopped."},"token_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token Error"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","token_valid","created_at"],"title":"GAAccountPublic","description":"A connected Google account. Addressed by id and named by label, never by address."},"GAComparisonPublic":{"properties":{"users":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"sessions":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"engagement_rate":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]}},"type":"object","title":"GAComparisonPublic"},"GAConnectionEntryPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"ga_property_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ga Property Id","description":"The linked GA4 property id, or null when no property is linked to this domain."},"ga_property_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ga Property Name","description":"Customer-set name of the linked property, or null when none is linked."},"account_token_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Account Token Valid","description":"Null when no property is linked - there is no token to check. False when the Google account behind the linked property's OAuth grant is invalid or expired: collection has stopped and last_collected_at is frozen."},"last_collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Collected At","description":"When this property's data was last collected from the GA4 API, or null when no property is linked or nothing has been collected yet."}},"type":"object","required":["domain_id"],"title":"GAConnectionEntryPublic","description":"One domain's GA linkage. A missing property is a distinct state from a dead token."},"GAConnectionsPublic":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"domains":{"items":{"$ref":"#/components/schemas/GAConnectionEntryPublic"},"type":"array","title":"Domains","description":"Every domain in workspace_id, whether or not a GA property is linked to it."}},"type":"object","required":["workspace_id","domains"],"title":"GAConnectionsPublic","description":"One grouped Postgres query, not domain_ids.length calls - see the route docstring."},"GADimensionsResponse":{"properties":{"measurement_basis":{"type":"string","title":"Measurement Basis","description":"Constant. How these Google Analytics figures relate to first-party Web Analytics.","default":"VitalSentinel Web Analytics and a linked Google Analytics 4 property count different populations, so their numbers will not match and must not be reconciled. The first-party script is cookieless and fires for every visitor; the GA4 tag is the customer's own and fires subject to their consent manager. Expect user counts to agree closely and pageviews, traffic channels and geography to diverge, sometimes several-fold - on one domain over one window, users agreed within 0.4 per cent while pageviews differed 2.5x and one country differed 73x. Neither source is wrong."},"account_token_valid":{"type":"boolean","title":"Account Token Valid","description":"False when the Google account behind this property's OAuth grant is invalid or expired. Collection has stopped, so every figure below is frozen as of last_collected_at rather than current \u2013 see 'notice'.","default":true},"last_collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Collected At","description":"When this property's data was last collected from the GA4 API. Stops advancing once account_token_valid is false, so compare it against range_end before treating a total as current."},"notice":{"type":"string","title":"Notice","description":"One plain sentence when account_token_valid is false; empty otherwise.","default":""},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date-time","title":"Range Start"},"range_end":{"type":"string","format":"date-time","title":"Range End"},"compare_range_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range Start"},"compare_range_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range End"},"period_dimension_totals":{"anyOf":[{"additionalProperties":{"additionalProperties":{"type":"number"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Period Dimension Totals","description":"Period-level totals straight from the GA4 API (not summed from daily rows), keyed dimension name -> dimension value -> metric value."},"compare_period_dimension_totals":{"anyOf":[{"additionalProperties":{"additionalProperties":{"type":"number"},"type":"object"},"type":"object"},{"type":"null"}],"title":"Compare Period Dimension Totals"},"period_dimension_total_counts":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Period Dimension Total Counts","description":"True count of distinct values per dimension, independent of 'limit'."}},"type":"object","required":["domain_id","range_start","range_end"],"title":"GADimensionsResponse"},"GAEventPoint":{"properties":{"date":{"type":"string","format":"date-time","title":"Date"},"event_name":{"type":"string","title":"Event Name"},"event_count":{"type":"integer","title":"Event Count"},"unique_users":{"type":"integer","title":"Unique Users"},"event_value":{"type":"number","title":"Event Value"}},"type":"object","required":["date","event_name","event_count","unique_users","event_value"],"title":"GAEventPoint"},"GAEventsPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GAEventPoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date-time","title":"Range Start"},"range_end":{"type":"string","format":"date-time","title":"Range End"},"total_events":{"type":"integer","title":"Total Events","description":"Count of rows in 'items'. Not a pre-limit total - 'limit' is a hard cap on the query, so this always equals len(items)."},"compare_range_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range Start"},"compare_range_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range End"},"compare_events":{"anyOf":[{"items":{"$ref":"#/components/schemas/GAEventPoint"},"type":"array"},{"type":"null"}],"title":"Compare Events"}},"type":"object","required":["items","domain_id","range_start","range_end","total_events"],"title":"GAEventsPage"},"GAMetricPoint":{"properties":{"date":{"type":"string","format":"date-time","title":"Date"},"metric_name":{"type":"string","title":"Metric Name"},"metric_value":{"type":"number","title":"Metric Value"},"dimension1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dimension1"},"dimension2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dimension2"}},"type":"object","required":["date","metric_name","metric_value"],"title":"GAMetricPoint"},"GAMetricsComparisonPublic":{"properties":{"users":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Users"},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate","description":"Fraction between 0 and 1."},"avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Session Duration Seconds"}},"type":"object","title":"GAMetricsComparisonPublic"},"GAMetricsPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GAMetricPoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date-time","title":"Range Start"},"range_end":{"type":"string","format":"date-time","title":"Range End"},"total_metrics":{"type":"integer","title":"Total Metrics","description":"Count of rows in 'items'. Not a pre-limit total - 'limit' is a hard cap on the query, so this always equals len(items)."},"compare_range_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range Start"},"compare_range_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range End"},"compare_metrics":{"anyOf":[{"items":{"$ref":"#/components/schemas/GAMetricPoint"},"type":"array"},{"type":"null"}],"title":"Compare Metrics"}},"type":"object","required":["items","domain_id","range_start","range_end","total_metrics"],"title":"GAMetricsPage"},"GAMetricsPublic":{"properties":{"users":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Users"},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate","description":"Fraction between 0 and 1."},"avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Session Duration Seconds"},"account_token_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Account Token Valid","description":"Whether the Google account behind the linked property has a valid OAuth token. Null when no GA4 property is linked at all (ga not in configured_modules). A dead token freezes these figures rather than zeroing them - the collector stops refreshing, so 'users: 0' from a broken connection reads identically to a genuinely quiet month unless this is checked."},"comparison":{"anyOf":[{"$ref":"#/components/schemas/GAMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"GAMetricsPublic"},"GAOverviewPublic":{"properties":{"users":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Users","description":"Active users over the last 28 days."},"sessions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate","description":"Fraction between 0 and 1."},"account_token_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Account Token Valid","description":"Whether the Google account behind the linked property has a valid OAuth token. Null when no GA4 property is linked at all. A dead token freezes these figures rather than zeroing them - the collector stops refreshing, so 'users: 0' from a broken connection looks identical to a genuinely quiet month unless this is checked."}},"type":"object","title":"GAOverviewPublic"},"GAPropertiesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GAPropertyPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"},"total_pages":{"type":"integer","title":"Total Pages"}},"type":"object","required":["items","page","per_page","total_pages"],"title":"GAPropertiesPage"},"GAPropertyPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"property_id":{"type":"string","title":"Property Id","description":"The GA4 property ID as it appears in Google Analytics."},"property_name":{"type":"string","title":"Property Name"},"website_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website Url"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Domain Id"},"account_token_valid":{"type":"boolean","title":"Account Token Valid","default":true},"linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Linked At"}},"type":"object","required":["id","property_id","property_name"],"title":"GAPropertyPublic"},"GATimelinePage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GATimelinePoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"measurement_basis":{"type":"string","title":"Measurement Basis","description":"Constant. How these Google Analytics figures relate to first-party Web Analytics.","default":"VitalSentinel Web Analytics and a linked Google Analytics 4 property count different populations, so their numbers will not match and must not be reconciled. The first-party script is cookieless and fires for every visitor; the GA4 tag is the customer's own and fires subject to their consent manager. Expect user counts to agree closely and pageviews, traffic channels and geography to diverge, sometimes several-fold - on one domain over one window, users agreed within 0.4 per cent while pageviews differed 2.5x and one country differed 73x. Neither source is wrong."},"account_token_valid":{"type":"boolean","title":"Account Token Valid","description":"False when the Google account behind this property's OAuth grant is invalid or expired. Collection has stopped, so every figure below is frozen as of last_collected_at rather than current \u2013 see 'notice'.","default":true},"last_collected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Collected At","description":"When this property's data was last collected from the GA4 API. Stops advancing once account_token_valid is false, so compare it against range_end before treating a total as current."},"notice":{"type":"string","title":"Notice","description":"One plain sentence when account_token_valid is false; empty otherwise.","default":""},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date-time","title":"Range Start"},"range_end":{"type":"string","format":"date-time","title":"Range End"},"compare_range_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range Start"},"compare_range_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compare Range End"},"compare_timeline":{"anyOf":[{"items":{"$ref":"#/components/schemas/GATimelinePoint"},"type":"array"},{"type":"null"}],"title":"Compare Timeline"},"period_total_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Total Users"},"period_new_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period New Users"},"period_active_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Active Users"},"period_total_sessions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Total Sessions"},"period_engaged_sessions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Engaged Sessions"},"period_avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Avg Engagement Rate"},"period_avg_bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Avg Bounce Rate"},"period_sessions_per_user":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Sessions Per User"},"period_avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Avg Session Duration Seconds"},"period_screen_page_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Screen Page Views"},"period_event_count":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period Event Count"},"compare_period_total_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Total Users"},"compare_period_new_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period New Users"},"compare_period_active_users":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Active Users"},"compare_period_total_sessions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Total Sessions"},"compare_period_engaged_sessions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Engaged Sessions"},"compare_period_avg_engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Avg Engagement Rate"},"compare_period_avg_bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Avg Bounce Rate"},"compare_period_sessions_per_user":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Sessions Per User"},"compare_period_avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Avg Session Duration Seconds"},"compare_period_screen_page_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Screen Page Views"},"compare_period_event_count":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Compare Period Event Count"}},"type":"object","required":["items","domain_id","range_start","range_end"],"title":"GATimelinePage"},"GATimelinePoint":{"properties":{"date":{"type":"string","format":"date-time","title":"Date"},"users":{"type":"number","title":"Users"},"sessions":{"type":"number","title":"Sessions"},"engagement_rate":{"type":"number","title":"Engagement Rate"},"bounce_rate":{"type":"number","title":"Bounce Rate"},"avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Session Duration Seconds"}},"type":"object","required":["date","users","sessions","engagement_rate","bounce_rate"],"title":"GATimelinePoint"},"GSCBrandPatternResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"pattern":{"type":"string","title":"Pattern"},"pattern_type":{"type":"string","enum":["contains","regex"],"title":"Pattern Type"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","domain_id","pattern","pattern_type","created_at"],"title":"GSCBrandPatternResponse"},"GSCBrandSegmentTotals":{"properties":{"queries":{"type":"integer","title":"Queries","description":"Distinct queries classified into this segment."},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr","description":"clicks / impressions for this segment, 0-1. 0 when no impressions."},"position":{"type":"number","title":"Position","description":"Impression-weighted average position for this segment. 0 when no impressions."},"clicks_share_pct":{"type":"number","title":"Clicks Share Pct","description":"This segment's clicks as a percentage of the window's total clicks."},"impressions_share_pct":{"type":"number","title":"Impressions Share Pct","description":"This segment's impressions as a percentage of the window's total impressions."}},"type":"object","required":["queries","clicks","impressions","ctr","position","clicks_share_pct","impressions_share_pct"],"title":"GSCBrandSegmentTotals","description":"One side of the branded/non-branded split, over every query in the window."},"GSCBrandTotals":{"properties":{"brand_source":{"type":"string","enum":["configured_patterns","domain_name_fallback"],"title":"Brand Source","description":"Which classifier produced this split, which decides how far it can be trusted. 'configured_patterns': this domain's own brand patterns (Settings > Search Console > Brand Patterns) - the customer's definition, and the same one the ctr_curve report segments with. 'domain_name_fallback': NO patterns are configured, so the split was derived from the domain's own registrable name as a single case-insensitive substring (edmac.eu gives 'edmac'). That fallback is a heuristic and not the customer's definition: it catches the obvious brand term and nothing else - no misspellings, no abbreviations, no product or sub-brand names - so brand is a FLOOR and nonbrand an over-count. Say which one you used when you quote these numbers. The whole field is absent only when brand_totals itself is null, which means nothing could classify this domain at all."},"brand":{"$ref":"#/components/schemas/GSCBrandSegmentTotals"},"nonbrand":{"$ref":"#/components/schemas/GSCBrandSegmentTotals"},"classified_clicks":{"type":"integer","title":"Classified Clicks","description":"brand.clicks + nonbrand.clicks - every click Search Console attributed to a query it was willing to name. The denominator behind clicks_share_pct."},"classified_impressions":{"type":"integer","title":"Classified Impressions","description":"brand.impressions + nonbrand.impressions. See classified_clicks."},"property_clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Property Clicks","description":"Clicks the whole property received in this window - the same figure get_search_performance reports, published here so the two can be reconciled in one response instead of looking like a disagreement. Always >= classified_clicks. Null when a page/country/device filter is active, since there is then no property-wide total to compare against."},"property_impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Property Impressions","description":"Impressions the whole property received. See property_clicks."},"clicks_coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clicks Coverage Pct","description":"classified_clicks as a percentage of property_clicks: how much of the property's search traffic can be attributed to a named query at all, and therefore how much of it this branded/non-branded split can speak for. Values well under 100 are normal and are Google withholding anonymised queries, not missing data on our side. Null when property_clicks is."},"impressions_coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Impressions Coverage Pct","description":"classified_impressions over property_impressions. See above."}},"type":"object","required":["brand_source","brand","nonbrand","classified_clicks","classified_impressions"],"title":"GSCBrandTotals","description":"Branded vs non-branded totals over every query in the window.\n\nClassified with the domain's configured brand patterns, pushed into ClickHouse so the\nsplit covers the whole property rather than a fetched page - the same classifier and\nthe same SQL translation the CTR-curve report segments with, so the two agree.\n\n**The two segments partition the classifiable population, which is smaller than the\nproperty total, and the difference is Google's, not ours.** Search Console withholds\nthe long tail of low-volume queries for privacy, so no query-level report ever sums to\nwhat the property actually received: on one real property these segments total 42,871\nclicks against the 137,085 ``get_search_performance`` reports for the identical window.\nBoth numbers are right. ``property_clicks``/``clicks_coverage_pct`` publish that gap as\na figure so it reads as coverage rather than as two tools contradicting each other -\nquote the shares, which are computed within the classifiable population, and quote the\ncoverage beside them if you quote the absolute counts at all."},"GSCCTRBenchmarkResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date","title":"Range Start","description":"Start of the window that actually produced these numbers - clamped to this property's real data_through, which may be earlier than what start_date/end_date requested."},"range_end":{"type":"string","format":"date","title":"Range End","description":"End of the window that actually produced these numbers - clamped to this property's real data_through, which may be earlier than what end_date requested. Equals data_through whenever the requested window ran past it."},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date with synced Search Console data for this property, within the originally requested window. GSC publishes with a 2-3 day lag; range_end above is clamped to this value so the numbers never silently average in empty days. Null when the property has no synced data at all in that window."},"average_position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Position","description":"Impression-weighted average position across the SAME restricted per-position buckets actual_ctr and expected_ctr are computed over (position 1-20, >=10 impressions per query/page/device group) - NOT this property's full position distribution, which /gsc/overview and get_search_performance report instead. Never the input to expected_ctr, which is already weighted across this same bucket population directly. Null when no bucket in the window qualifies."},"actual_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Actual Ctr","description":"Domain's actual CTR for the window, as a ratio 0-1, aggregated across the same per-position buckets expected_ctr and average_position are computed over - so all three are directly comparable. See property_ctr for the unrestricted, property-wide CTR /gsc/overview and get_search_performance report instead, and bucket_coverage_pct for how much of the window's traffic this restricted population actually covers - a low coverage, not a measurement error, is why the two CTR figures can differ substantially. NULL when no bucket in the window qualifies at all (the same condition that nulls average_position and sets source 'none'), which is the normal state for a small property whose every query/page/device group falls under the impression floor. Null means 'this benchmark has nothing to compare' - never read it as a measured zero, and check property_ctr, which can be a healthy non-zero figure in exactly that case."},"property_ctr":{"type":"number","title":"Property Ctr","description":"This property's CTR across its ENTIRE position distribution for the window, as a ratio 0-1 - the same figure /gsc/overview's average_ctr and get_search_performance's average_ctr_pct report. Unlike actual_ctr above, this is never restricted to position 1-20 or to query/page/device groups clearing the impression floor, so quote THIS figure as 'the domain's CTR' and actual_ctr as 'CTR within the compared population' - never the reverse."},"bucket_coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bucket Coverage Pct","description":"Share (0-100) of the window's property-wide impressions that fell inside the buckets actual_ctr/average_position/expected_ctr are computed over. This is the entire explanation for why actual_ctr can diverge from property_ctr: a domain with a long tail of impressions past position 20, or many query/page groups too thin to clear the impression floor, has real traffic this benchmark never sees. Null when the property had zero impressions in the window."},"expected_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Expected Ctr","description":"Expected CTR for the window, as a ratio 0-1: impression-weighted across the same per-position buckets actual_ctr is aggregated over (never a single curve evaluation at average_position - see the class description for why). Source is whichever benchmark answered (see 'source'). Null when there is no qualifying bucket in the window or neither source could resolve a value."},"ctr_gap_pp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr Gap Pp","description":"actual_ctr minus expected_ctr, in PERCENTAGE POINTS (already multiplied by 100 - not the same units as actual_ctr/expected_ctr above). POSITIVE means this domain is outperforming the benchmark, NEGATIVE means it is underperforming. Null unless expected_ctr is present."},"ctr_gap_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr Gap Pct","description":"The same gap expressed as a percentage RELATIVE TO expected_ctr - 'how much better or worse', not 'how many points'. Same sign convention as ctr_gap_pp. Null unless expected_ctr is present and non-zero."},"source":{"type":"string","enum":["fitted_curve","industry_benchmark","none"],"title":"Source","description":"Which benchmark answered. 'fitted_curve' is this property's own history-fitted CTR-vs-position curve - the more accurate source, when available. 'industry_benchmark' is the quarterly AWR industry reference, used only when no fitted curve is cached for this property yet. 'none' when neither source could resolve a value. Never mistake an industry curve for this domain's own history - check this field before quoting expected_ctr as if it were the domain's own data."},"fitted_curve_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fitted Curve Age Seconds","description":"Age of the fitted curve in seconds. Present only when source='fitted_curve'."},"industry_benchmark_quarter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry Benchmark Quarter","description":"AWR quarter tag, e.g. '2026-Q1'. Present only when source='industry_benchmark'."}},"type":"object","required":["domain_id","range_start","range_end","property_ctr","source"],"title":"GSCCTRBenchmarkResponse","description":"Is this domain's CTR good, benchmarked against its own history or an industry reference.\n\nA read-only alternative to the `ctr_curve` report (`POST /gsc/reports/run`), which spends\ncredits and runs asynchronously - this answers the same \"is my CTR good\" question from\ndata that is already sitting in cache or a small reference table, synchronously.\n\nactual_ctr, expected_ctr and average_position are all computed over the SAME restricted\npopulation (position 1-20, >=10 impressions per query/page/device group) - never a mix of\na bucket-scoped figure and an unrestricted one. actual_ctr and expected_ctr are both\nimpression-weighted across this window's real per-position distribution, not evaluated at\na single average position - the CTR curve is convex, so a single-point evaluation\nunderstates the true expectation whenever the position distribution has spread, which most\ndomains' does. property_ctr and bucket_coverage_pct expose the unrestricted, property-wide\nfigure and how much of the window's traffic the restricted population actually covers.\n\nAll three bucket-scoped figures are null together when the window has no qualifying\nbucket - a property too small for any query/page/device group to clear the impression\nfloor still has a real property_ctr, and this benchmark simply has nothing to compare it\nagainst."},"GSCComparisonPublic":{"properties":{"web":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeComparisonPublic"},{"type":"null"}]},"discover":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeComparisonPublic"},{"type":"null"}]},"news":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeComparisonPublic"},{"type":"null"}]}},"type":"object","title":"GSCComparisonPublic"},"GSCCountriesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCCountryRow"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Range Start","description":"First day of the window these rows cover. Present so a breakdown can be reconciled against the property total from GET .../gsc/overview - see the same field on the device breakdown for what its absence cost."},"range_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Range End","description":"Last day of the window these rows cover, inclusive."},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date with synced Search Console data in this window. GSC publishes with a 2-3 day lag, so a window whose last couple of days are thin or absent is not necessarily a real drop - check this before reading one. Null when the window has no synced data at all."}},"type":"object","required":["items","domain_id"],"title":"GSCCountriesPage"},"GSCCountryRow":{"properties":{"country":{"type":"string","title":"Country"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"}},"type":"object","required":["country","clicks","impressions","ctr","position"],"title":"GSCCountryRow"},"GSCDeviceRow":{"properties":{"device":{"type":"string","title":"Device"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"}},"type":"object","required":["device","clicks","impressions","ctr","position"],"title":"GSCDeviceRow"},"GSCDevicesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCDeviceRow"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Range Start","description":"First day of the window these rows cover. Present so a breakdown can be reconciled against the property total from GET .../gsc/overview: this endpoint used to publish no window at all, and its unstated default differed from the overview's, so the three device rows summed to MORE clicks than the whole property reported for what looked like the same period."},"range_end":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Range End","description":"Last day of the window these rows cover, inclusive."},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date with synced Search Console data in this window. GSC publishes with a 2-3 day lag, so a window whose last couple of days are thin or absent is not necessarily a real drop - check this before reading one. Null when the window has no synced data at all."}},"type":"object","required":["items","domain_id"],"title":"GSCDevicesPage"},"GSCExperimentCreate":{"properties":{"name":{"type":"string","maxLength":256,"minLength":1,"title":"Name"},"hypothesis":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Hypothesis"},"control_urls":{"items":{"type":"string"},"type":"array","maxItems":1000,"minItems":1,"title":"Control Urls"},"variant_urls":{"items":{"type":"string"},"type":"array","maxItems":1000,"minItems":1,"title":"Variant Urls"},"pre_start_date":{"type":"string","format":"date","title":"Pre Start Date"},"pre_end_date":{"type":"string","format":"date","title":"Pre End Date"},"post_start_date":{"type":"string","format":"date","title":"Post Start Date"},"post_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post End Date"},"primary_metric":{"type":"string","enum":["clicks","ctr","position","impressions"],"title":"Primary Metric"},"method":{"type":"string","enum":["mann_whitney","did","bsts"],"title":"Method","default":"mann_whitney"},"status":{"type":"string","enum":["draft","running","concluded","abandoned"],"title":"Status","default":"draft"}},"type":"object","required":["name","control_urls","variant_urls","pre_start_date","pre_end_date","post_start_date","primary_metric"],"title":"GSCExperimentCreate"},"GSCExperimentEvaluationResponse":{"properties":{"experiment_id":{"type":"string","format":"uuid","title":"Experiment Id"},"evaluation":{"additionalProperties":true,"type":"object","title":"Evaluation"},"evaluated_at":{"type":"string","format":"date-time","title":"Evaluated At"},"status":{"type":"string","title":"Status","description":"'pending', 'completed', or 'failed'."}},"type":"object","required":["experiment_id","evaluation","evaluated_at","status"],"title":"GSCExperimentEvaluationResponse"},"GSCExperimentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"name":{"type":"string","title":"Name"},"hypothesis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hypothesis"},"control_urls":{"items":{"type":"string"},"type":"array","title":"Control Urls"},"variant_urls":{"items":{"type":"string"},"type":"array","title":"Variant Urls"},"pre_start_date":{"type":"string","format":"date","title":"Pre Start Date"},"pre_end_date":{"type":"string","format":"date","title":"Pre End Date"},"post_start_date":{"type":"string","format":"date","title":"Post Start Date"},"post_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Post End Date"},"primary_metric":{"type":"string","enum":["clicks","ctr","position","impressions"],"title":"Primary Metric"},"method":{"type":"string","enum":["mann_whitney","did","bsts"],"title":"Method"},"status":{"type":"string","enum":["draft","running","concluded","abandoned"],"title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","name","control_urls","variant_urls","pre_start_date","pre_end_date","post_start_date","primary_metric","method","status","created_at","updated_at"],"title":"GSCExperimentResponse"},"GSCHubKPIs":{"properties":{"total_clicks":{"type":"integer","title":"Total Clicks"},"total_impressions":{"type":"integer","title":"Total Impressions"},"avg_ctr":{"type":"number","title":"Avg Ctr"},"avg_position":{"type":"number","title":"Avg Position"},"delta_clicks_pct":{"type":"number","title":"Delta Clicks Pct"},"delta_impressions_pct":{"type":"number","title":"Delta Impressions Pct"}},"type":"object","required":["total_clicks","total_impressions","avg_ctr","avg_position","delta_clicks_pct","delta_impressions_pct"],"title":"GSCHubKPIs"},"GSCHubSummaryPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCReportTile"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"kpis":{"anyOf":[{"$ref":"#/components/schemas/GSCHubKPIs"},{"type":"null"}],"description":"Null when the property has no data in the trailing 28 days."},"computed_at":{"type":"string","format":"date-time","title":"Computed At"}},"type":"object","required":["items","domain_id","computed_at"],"title":"GSCHubSummaryPage"},"GSCKnownEventResponse":{"properties":{"id":{"type":"integer","title":"Id"},"domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Domain Id","description":"Null for a global event (visible on every domain)."},"event_date":{"type":"string","format":"date","title":"Event Date"},"event_type":{"type":"string","enum":["core_update","spam_update","helpful_content","site_migration","custom"],"title":"Event Type"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","event_date","event_type","title","created_at"],"title":"GSCKnownEventResponse"},"GSCLocaleConfig":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"strategy":{"type":"string","enum":["path","subdomain","hreflang_map","tld"],"title":"Strategy"},"rules":{"additionalProperties":true,"type":"object","title":"Rules"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["domain_id","strategy","rules","updated_at"],"title":"GSCLocaleConfig"},"GSCMetricsComparisonPublic":{"properties":{"web":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]},"discover":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]},"news":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]}},"type":"object","title":"GSCMetricsComparisonPublic"},"GSCMetricsPublic":{"properties":{"web":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]},"discover":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]},"news":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypeMetricsPublic"},{"type":"null"}]},"comparison":{"anyOf":[{"$ref":"#/components/schemas/GSCMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"GSCMetricsPublic"},"GSCNewRankingRow":{"properties":{"value":{"type":"string","title":"Value","description":"The query text or page URL, per 'ranking_type'."},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"}},"type":"object","required":["value","clicks","impressions","ctr","position"],"title":"GSCNewRankingRow"},"GSCNewRankingsPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCNewRankingRow"},"type":"array","title":"Items","description":"Rows with at least one impression in the requested range and zero in the immediately preceding range of the same length - 'new' means 'absent from the prior period', not 'never seen before'."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date","title":"Range Start"},"range_end":{"type":"string","format":"date","title":"Range End"},"ranking_type":{"type":"string","enum":["queries","pages"],"title":"Ranking Type","description":"Whether 'items' holds queries or pages."}},"type":"object","required":["items","domain_id","range_start","range_end","ranking_type"],"title":"GSCNewRankingsPage"},"GSCOverviewPublic":{"properties":{"web":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypePublic"},{"type":"null"}]},"discover":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypePublic"},{"type":"null"}]},"news":{"anyOf":[{"$ref":"#/components/schemas/GSCSearchTypePublic"},{"type":"null"}]}},"type":"object","title":"GSCOverviewPublic","description":"Last 28 days per search type.\n\n``discover`` and ``news`` are null below 50 clicks in the window, matching the\ndashboard: at that volume the derived rates are noise rather than signal."},"GSCOverviewResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"property_url":{"type":"string","title":"Property Url"},"range_start":{"type":"string","format":"date","title":"Range Start"},"range_end":{"type":"string","format":"date","title":"Range End"},"total_clicks":{"type":"integer","title":"Total Clicks"},"total_impressions":{"type":"integer","title":"Total Impressions"},"average_ctr":{"type":"number","title":"Average Ctr"},"average_position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Position"},"total_queries":{"type":"integer","title":"Total Queries"},"total_pages":{"type":"integer","title":"Total Pages"},"last_updated":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Last Updated","description":"Most recent date GSC data was collected for this property."},"prev_total_clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Total Clicks","description":"total_clicks for the immediately preceding window of equal length. Null unless compare_previous=true."},"prev_total_impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Total Impressions","description":"total_impressions for the immediately preceding window of equal length. Null unless compare_previous=true."},"prev_average_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Average Ctr","description":"average_ctr for the immediately preceding window of equal length. Null unless compare_previous=true."},"prev_average_position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Average Position","description":"average_position for the immediately preceding window of equal length. Null unless compare_previous=true."},"total_clicks_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Clicks Change Pct","description":"Percent change in total_clicks vs the immediately preceding window of equal length. Null unless compare_previous=true."},"total_impressions_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Impressions Change Pct","description":"Percent change in total_impressions vs the immediately preceding window of equal length. Null unless compare_previous=true."},"average_ctr_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Ctr Change Pct","description":"Percent change in average_ctr vs the immediately preceding window of equal length. Null unless compare_previous=true."},"average_position_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Average Position Change Pct","description":"Percent change in average_position vs the immediately preceding window of equal length. Position is inverted - LOWER is better - so a NEGATIVE value means position improved (moved up the results) and a POSITIVE value means it got worse. Null unless compare_previous=true."},"comparison_coverage":{"anyOf":[{"$ref":"#/components/schemas/ComparisonCoveragePublic"},{"type":"null"}],"description":"Whether the two windows behind every *_change_pct field hold comparable amounts of data. Present only when a comparison ran. Read like_for_like before quoting any change figure: a current window running to now is routinely part-empty against a fully-synced baseline, which makes the percentage arithmetically right and factually false."}},"type":"object","required":["domain_id","property_url","range_start","range_end","total_clicks","total_impressions","average_ctr","total_queries","total_pages"],"title":"GSCOverviewResponse"},"GSCPageRow":{"properties":{"prev_clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Clicks","description":"clicks for the immediately preceding window of equal length."},"prev_impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Impressions","description":"impressions for the immediately preceding window of equal length."},"prev_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Ctr","description":"ctr for the immediately preceding window of equal length."},"prev_position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Position","description":"position for the immediately preceding window of equal length."},"clicks_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clicks Change Pct","description":"Percent change in clicks vs the immediately preceding window."},"impressions_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Impressions Change Pct","description":"Percent change in impressions vs the immediately preceding window."},"ctr_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr Change Pct","description":"Percent change in ctr vs the immediately preceding window."},"position_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Position Change Pct","description":"Percent change in position vs the immediately preceding window of equal length. Position is inverted - LOWER is better - so a NEGATIVE value is an improvement and a POSITIVE value is a regression."},"page":{"type":"string","title":"Page"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"}},"type":"object","required":["page","clicks","impressions","ctr","position"],"title":"GSCPageRow"},"GSCPagesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCPageRow"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date","title":"Range Start"},"range_end":{"type":"string","format":"date","title":"Range End"},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date with synced Search Console data in this window. GSC publishes with a 2-3 day lag, so a window whose last couple of days are thin or absent is not necessarily a real drop - check this before reading one. Null when the window has no synced data at all."},"ranking_scope":{"type":"string","enum":["property","fetched_page"],"title":"Ranking Scope","description":"Whether 'items' order is a genuine property-wide ranking ('property') or only an ordering within the up-to-'limit' rows this call fetched ('fetched_page'). Always 'fetched_page' when sort_by is one of the four *_change_pct keys: ranking by period-over-period change re-sorts the rows this call already fetched by clicks, in Python, because the comparison needs a previous-window join this endpoint's SQL cannot run at the whole-property scale. Raising 'limit' widens that candidate set and can change which row comes out on top - 'total' above still reports the property's true row count regardless, since a re-sort does not filter anything, it just does not describe this ranking. The candidate set itself is always selected by CURRENT clicks, so a row that fell to zero clicks this window can never appear here at any limit, even if it is the property's single biggest decliner - inherent to this design, not a bug. 'property' for every other sort_by, where ClickHouse orders and limits the whole property directly, so 'total' and the ranking agree."},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"The 'offset' that resumes reading after this page, or null when has_more is false. Use it rather than adding len(items) to your own offset: 'offset' counts rows before any of the response-side filters (brand, declining_only) ran, so a page that returned 40 of 1000 fetched rows still advances by 1000 - advancing by 40 re-reads 960 rows already seen and double-counts them. Null with has_more true is never returned."},"comparison_coverage":{"anyOf":[{"$ref":"#/components/schemas/ComparisonCoveragePublic"},{"type":"null"}],"description":"Whether the two windows behind every row's *_change_pct fields hold comparable amounts of data. Present only when a comparison ran. Read like_for_like before quoting a change figure or a declining_only result."}},"type":"object","required":["items","domain_id","range_start","range_end","ranking_scope"],"title":"GSCPagesPage"},"GSCPerformancePage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCPerformancePoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date","title":"Range Start"},"range_end":{"type":"string","format":"date","title":"Range End"},"search_type":{"type":"string","title":"Search Type"},"total_rows":{"type":"integer","title":"Total Rows","description":"Count of daily points in 'items'. Not a pre-limit total - the query applies 'limit' as a hard cap, so this always equals len(items)."}},"type":"object","required":["items","domain_id","range_start","range_end","search_type","total_rows"],"title":"GSCPerformancePage"},"GSCPerformancePoint":{"properties":{"date":{"type":"string","format":"date","title":"Date"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr","description":"Click-through rate as a ratio 0-1, e.g. 0.034 is 3.4%."},"position":{"type":"number","title":"Position","description":"Average search results position; 1 is the top result."}},"type":"object","required":["date","clicks","impressions","ctr","position"],"title":"GSCPerformancePoint"},"GSCQueriesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCQueryRow"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"range_start":{"type":"string","format":"date","title":"Range Start"},"range_end":{"type":"string","format":"date","title":"Range End"},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date with synced Search Console data in this window. GSC publishes with a 2-3 day lag, so a window whose last couple of days are thin or absent is not necessarily a real drop - check this before reading one. Null when the window has no synced data at all."},"brand_patterns_configured":{"type":"boolean","title":"Brand Patterns Configured","description":"Whether this domain has brand patterns of its own configured (Settings > Search Console > Brand Patterns). False does NOT mean nothing was classified: the public surface then derives a single pattern from the domain's own name so the branded-versus-non-branded question stays answerable, and brand_totals.brand_source reports which of the two ran. Read brand_source, not this flag, to decide how far to trust the split; this flag only answers 'is this the customer's own definition'."},"ranking_scope":{"type":"string","enum":["property","fetched_page"],"title":"Ranking Scope","description":"Whether 'items' order is a genuine property-wide ranking ('property') or only an ordering within the up-to-'limit' rows this call fetched ('fetched_page'). Always 'fetched_page' when sort_by is one of the four *_change_pct keys: ranking by period-over-period change re-sorts the rows this call already fetched by clicks, in Python, because the comparison needs a previous-window join this endpoint's SQL cannot run at the whole-property scale. Raising 'limit' widens that candidate set and can change which row comes out on top - 'total' above still reports the property's true row count regardless, since a re-sort does not filter anything, it just does not describe this ranking. The candidate set itself is always selected by CURRENT clicks, so a row that fell to zero clicks this window can never appear here at any limit, even if it is the property's single biggest decliner - inherent to this design, not a bug. 'property' for every other sort_by, where ClickHouse orders and limits the whole property directly, so 'total' and the ranking agree."},"min_impressions_applied":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Impressions Applied","description":"Impression floor actually applied before ranking, or null when none was. Applied by default (see get_queries' min_impressions parameter for the value) whenever sort_by=ctr and the request set neither an explicit min_impressions nor an exact-match 'query' filter - a CTR computed from a single impression is a coincidence, not a ranking, the same failure mode get_rum_pages's min_pageviews_applied exists to prevent for percentile metrics. An explicit min_impressions is echoed back verbatim, whatever sort_by is."},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"The 'offset' that resumes reading after this page, or null when has_more is false. Use it rather than adding len(items) to your own offset: 'offset' counts rows before any of the response-side filters (brand, declining_only) ran, so a page that returned 40 of 1000 fetched rows still advances by 1000 - advancing by 40 re-reads 960 rows already seen and double-counts them. Null with has_more true is never returned."},"brand_totals":{"anyOf":[{"$ref":"#/components/schemas/GSCBrandTotals"},{"type":"null"}],"description":"Branded vs non-branded totals for the WHOLE window, computed server-side over every query - not over the rows returned. Present whenever this domain has brand patterns configured (see brand_patterns_configured), independent of whether the 'brand' filter is set. This is the field to read for 'how much of my search traffic is branded': summing is_brand over a page of rows answers it only for that page, and once the brand filter runs 'total' is null by design, so a hand-sum has no denominator to check itself against. Which of this request's filters it honours, stated exactly so it is never read as disagreeing with the rows: it applies the same window, search_type, query_contains, page, country and device this call used, so the two describe the same population. It deliberately IGNORES min_impressions, position_min, position_max, declining_only and 'brand' itself - those select a subset of queries to rank, and a branded share computed over a subset would not answer the question this field exists for. So with min_impressions set, brand.clicks + nonbrand.clicks can exceed the clicks visible in 'items'."},"comparison_coverage":{"anyOf":[{"$ref":"#/components/schemas/ComparisonCoveragePublic"},{"type":"null"}],"description":"Whether the two windows behind every row's *_change_pct fields hold comparable amounts of data. Present only when a comparison ran. Read like_for_like before quoting a change figure or a declining_only result."}},"type":"object","required":["items","domain_id","range_start","range_end","brand_patterns_configured","ranking_scope"],"title":"GSCQueriesPage"},"GSCQueryRow":{"properties":{"prev_clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Clicks","description":"clicks for the immediately preceding window of equal length."},"prev_impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Impressions","description":"impressions for the immediately preceding window of equal length."},"prev_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Ctr","description":"ctr for the immediately preceding window of equal length."},"prev_position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev Position","description":"position for the immediately preceding window of equal length."},"clicks_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clicks Change Pct","description":"Percent change in clicks vs the immediately preceding window."},"impressions_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Impressions Change Pct","description":"Percent change in impressions vs the immediately preceding window."},"ctr_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr Change Pct","description":"Percent change in ctr vs the immediately preceding window."},"position_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Position Change Pct","description":"Percent change in position vs the immediately preceding window of equal length. Position is inverted - LOWER is better - so a NEGATIVE value is an improvement and a POSITIVE value is a regression."},"query":{"type":"string","title":"Query"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"},"is_brand":{"type":"boolean","title":"Is Brand","description":"Whether this query was classified as branded. Read brand_totals.brand_source on the page before trusting it: with brand patterns configured this is the customer's own definition (the same classifier the ctr_curve report segments with); with none configured it falls back to the domain's own name as a substring, a heuristic that under-counts. When brand_totals is null nothing could be classified at all and this is false on every row - that means 'nothing to classify against', not 'not branded'."}},"type":"object","required":["query","clicks","impressions","ctr","position","is_brand"],"title":"GSCQueryRow"},"GSCRegisteredReport":{"properties":{"key":{"type":"string","title":"Key"},"tier":{"type":"string","title":"Tier"},"freshness_seconds":{"type":"integer","title":"Freshness Seconds","description":"A completed run younger than this is served from cache on the next run request."}},"type":"object","required":["key","tier","freshness_seconds"],"title":"GSCRegisteredReport"},"GSCReportRunRequest":{"properties":{"params":{"additionalProperties":true,"type":"object","title":"Params"},"force":{"type":"boolean","title":"Force","description":"Bypass the freshness cache and force a new compute.","default":false}},"type":"object","title":"GSCReportRunRequest"},"GSCReportRunResponse":{"properties":{"run_id":{"type":"string","format":"uuid","title":"Run Id"},"report_key":{"type":"string","title":"Report Key"},"status":{"type":"string","enum":["pending","running","completed","failed"],"title":"Status"},"cached":{"type":"boolean","title":"Cached","description":"True if this run was served from the freshness cache."},"params":{"additionalProperties":true,"type":"object","title":"Params"},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"result":{"anyOf":[{},{"type":"null"}],"title":"Result","description":"The report's computed payload; shape is report-specific."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"requested_at":{"type":"string","format":"date-time","title":"Requested At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"}},"type":"object","required":["run_id","report_key","status","cached","params","requested_at"],"title":"GSCReportRunResponse"},"GSCReportRunRowsPage":{"properties":{"items":{"items":{},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"run_id":{"type":"string","format":"uuid","title":"Run Id"},"report_key":{"type":"string","title":"Report Key"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","run_id","report_key","offset","limit"],"title":"GSCReportRunRowsPage"},"GSCReportTile":{"properties":{"report_key":{"type":"string","title":"Report Key"},"has_run":{"type":"boolean","title":"Has Run"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"preview":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preview","description":"Per-report summary snippet; shape is report-specific."}},"type":"object","required":["report_key","has_run"],"title":"GSCReportTile"},"GSCSearchAppearancePage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCSearchAppearanceRow"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"}},"type":"object","required":["items","domain_id"],"title":"GSCSearchAppearancePage"},"GSCSearchAppearanceRow":{"properties":{"search_appearance":{"type":"string","title":"Search Appearance"},"clicks":{"type":"integer","title":"Clicks"},"impressions":{"type":"integer","title":"Impressions"},"ctr":{"type":"number","title":"Ctr"},"position":{"type":"number","title":"Position"}},"type":"object","required":["search_appearance","clicks","impressions","ctr","position"],"title":"GSCSearchAppearanceRow"},"GSCSearchTypeComparisonPublic":{"properties":{"clicks":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"impressions":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_int_"},{"type":"null"}]},"ctr":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}]},"position":{"anyOf":[{"$ref":"#/components/schemas/MetricChange_float_"},{"type":"null"}],"description":"Web search only. A negative change means ranking improved."}},"type":"object","title":"GSCSearchTypeComparisonPublic"},"GSCSearchTypeMetricsPublic":{"properties":{"clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clicks"},"impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Impressions"},"ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr","description":"Fraction between 0 and 1, e.g. 0.034 means 3.4%."},"position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Position","description":"Average search result position."}},"type":"object","title":"GSCSearchTypeMetricsPublic"},"GSCSearchTypePublic":{"properties":{"clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clicks"},"impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Impressions"},"ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr","description":"Fraction between 0 and 1, e.g. 0.034 means 3.4%."},"position":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Position","description":"Impression-weighted average result position. Web search only - Search Console does not report a position for Discover or News."}},"type":"object","title":"GSCSearchTypePublic"},"GSCURLPattern":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"template_label":{"type":"string","title":"Template Label"},"url_regex":{"type":"string","title":"Url Regex"},"priority":{"type":"integer","title":"Priority"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","domain_id","template_label","url_regex","priority","created_at"],"title":"GSCURLPattern"},"GeoMetrics":{"properties":{"country_code":{"type":"string","title":"Country Code"},"country_name":{"type":"string","title":"Country Name"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"Only the /geographic endpoint resolves regions."},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","description":"Share of the window's visitors.","default":0.0}},"type":"object","required":["country_code","country_name"],"title":"GeoMetrics","description":"One country's traffic metrics.\n\nEvery field except ``region`` is populated on both ``/geographic`` and\n``OverviewPublic.top_countries``, and both compute them the same way, so the two agree\nrow for row. They did not: the overview left ``pageviews``, ``bounce_rate`` and\n``percentage`` at these defaults, so it reported 0 pageviews and a 0 bounce rate for a\ncountry that ``/geographic`` credited with 361,852 pageviews in the same window.\nDefaulting to 0 is what made that invisible - a measured zero and an unset field looked\nidentical - so anything genuinely unavailable here is ``None``, never ``0``."},"GeographicDistributionPublic":{"properties":{"country":{"type":"string","title":"Country"},"session_count":{"type":"integer","title":"Session Count","description":"Sessions that measured at least one of LCP, CLS or INP - the denominator for the three percentages below. Sessions that measured none of the three are excluded, because they cannot be classified."},"avg_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Lcp Ms"},"avg_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Inp Ms"},"avg_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Cls"},"avg_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Fcp Ms"},"avg_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Ttfb Ms"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"},"p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Ms"},"p75_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Cls"},"p75_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Fcp Ms"},"p75_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Ttfb Ms"},"good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Good Pct"},"needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Needs Improvement Pct"},"poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Poor Pct"}},"type":"object","required":["country","session_count"],"title":"GeographicDistributionPublic","description":"One country's real-user performance.\n\nThe two halves of this row are computed over different populations and are not meant\nto be reconciled arithmetically. ``good_pct``/``needs_improvement_pct``/``poor_pct``\nare a session-level verdict: each session is scored against every CWV it measured, and\na metric it never reported is not held against it. The ``p75_*`` columns are ordinary\nper-metric percentiles over the events that did report that metric. Where a country's\ntraffic mostly reports CLS and INP but rarely LCP, a healthy ``good_pct`` can therefore\nsit beside a poor ``p75_lcp_ms`` - both are correct, and the p75 is the one describing\nLCP."},"GeographicPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GeoMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"continents":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContinentMetrics"},"type":"array"},{"type":"null"}],"title":"Continents"},"languages":{"anyOf":[{"items":{"$ref":"#/components/schemas/LanguageMetrics"},"type":"array"},{"type":"null"}],"title":"Languages"},"totals":{"$ref":"#/components/schemas/GeographicTotalsPublic"},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that recorded a session (YYYY-MM-DD), or null when the window holds none. The same value GET /analytics/overview reports for the identical window, so a caller chaining one into the other is not told about a truncated window once and left to assume the second answer is complete. Check it before quoting a ranking as the window's ranking."}},"type":"object","required":["items"],"title":"GeographicPublic"},"GeographicPublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GeographicDistributionPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date","description":"Same value as window_start below - kept for backward compatibility."},"end_date":{"type":"string","format":"date-time","title":"End Date","description":"Same value as window_end below - kept for backward compatibility."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"min_sessions_applied":{"type":"integer","title":"Min Sessions Applied","description":"Session floor actually applied before sort_by ranked the countries below - a country with fewer sessions than this was dropped. Defaults to 20: below that a 75th-percentile value is mostly noise (a single-session country topped a worst-LCP ranking before this floor existed), matching the min_sessions floor used elsewhere in the codebase for the same reason (app/services/gsc_reports/reports/post_click_engagement.py). Pass min_sessions=1 to see every country with any data at all, including single-visit ones.","default":20}},"type":"object","required":["items","domain_id","start_date","end_date","window_start","window_end"],"title":"GeographicPublicResponse"},"GeographicTotalsPublic":{"properties":{"countries":{"type":"integer","title":"Countries","default":0},"visitors":{"type":"integer","title":"Visitors","default":0},"visitors_events":{"type":"integer","title":"Visitors Events","description":"Unique visitors from the events table; the denominator for language percentages.","default":0}},"type":"object","title":"GeographicTotalsPublic"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HasDataPublic":{"properties":{"has_data":{"type":"boolean","title":"Has Data"}},"type":"object","required":["has_data"],"title":"HasDataPublic"},"HealthCheckResponse":{"properties":{"status":{"type":"string","title":"Status","description":"Always 'ok' - a non-200 status code is the failure signal."}},"type":"object","required":["status"],"title":"HealthCheckResponse","description":"Body of the unauthenticated liveness probe."},"INPScriptDomainItemPublic":{"properties":{"script_url":{"type":"string","title":"Script Url"},"count":{"type":"integer","title":"Count"},"p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Ms"}},"type":"object","required":["script_url","count"],"title":"INPScriptDomainItemPublic"},"INPScriptDomainsPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/INPScriptDomainItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"INPScriptDomainsPageResponse"},"INPSubpartsTimelineItemPublic":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"avg_input_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Input Delay Ms"},"avg_processing_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Processing Time Ms"},"avg_presentation_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Presentation Delay Ms"}},"type":"object","title":"INPSubpartsTimelineItemPublic"},"INPSubpartsTimelinePageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/INPSubpartsTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"INPSubpartsTimelinePageResponse"},"IncidentPublic":{"properties":{"started_at":{"type":"string","format":"date-time","title":"Started At"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"status_code":{"type":"integer","title":"Status Code"},"error_type":{"type":"string","title":"Error Type"},"error_class":{"type":"string","title":"Error Class","description":"'5xx' | '4xx' | 'timeout' | 'dns' | 'tls' | 'other'"},"is_ongoing":{"type":"boolean","title":"Is Ongoing"}},"type":"object","required":["started_at","status_code","error_type","error_class","is_ongoing"],"title":"IncidentPublic"},"IncidentsPagePublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/IncidentPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"class_counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Class Counts","description":"Incident counts per error_class across the full window - unless the window's 2000-incident fetch cap was hit (see 'total'), in which case these counts, like 'items', only cover the most recent 2000 incidents actually fetched, not the whole requested window. When the request passed error_class explicitly and the fetch cap was NOT hit, that class is always a key here, even at 0 - a zero count in this window is not the same fact as a class that was never counted. Once the fetch cap is hit, an explicitly requested class missing from this map was simply not among the most recent 2000 fetched - that is not a confirmed zero, so it is left out rather than asserted as 0."}},"type":"object","required":["items","domain_id","start_date","end_date"],"title":"IncidentsPagePublic","description":"``total`` is the exact count of matching incidents (post ``error_class`` filter)\nonly while the internal fetch stayed under its 2000-incident window cap. Once that cap\nis hit, older incidents were excluded before filtering/counting ever ran, so ``total``\nis no longer a real count - it goes absent (``Page.total``: \"absent otherwise\") and\n``has_more`` is unconditionally true, even past the last page this endpoint can serve.\nSee the ``_INCIDENTS_WINDOW_CAP`` comment in ``app/api/public/v1/uptime.py``."},"IndexingDiagnosticsPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"gsc_linked":{"type":"boolean","title":"Gsc Linked"},"sitemap_urls":{"type":"integer","title":"Sitemap Urls","description":"Rows in sitemap_discovered_urls with status='success'."},"inspection_stats":{"anyOf":[{"$ref":"#/components/schemas/InspectionStats"},{"type":"null"}]},"daily_inspections":{"items":{"$ref":"#/components/schemas/DailyInspectionCount"},"type":"array","title":"Daily Inspections"},"analysis":{"anyOf":[{"$ref":"#/components/schemas/DiagnosticsAnalysis"},{"type":"null"}]}},"type":"object","required":["domain_id","gsc_linked","sitemap_urls"],"title":"IndexingDiagnosticsPublic"},"IndexingOverviewPublic":{"properties":{"total_inspections":{"type":"integer","title":"Total Inspections","default":0},"submitted_and_indexed":{"type":"integer","title":"Submitted And Indexed","default":0},"crawled_not_indexed":{"type":"integer","title":"Crawled Not Indexed","default":0},"discovered_not_indexed":{"type":"integer","title":"Discovered Not Indexed","default":0}},"type":"object","title":"IndexingOverviewPublic","description":"Coverage counts from the latest Search Console inspection of each URL.\n\nThe three named states do not sum to ``total_inspections``: every other coverage\nstate (redirects, noindex, canonical duplicates, ...) is counted in the total but has\nno field of its own here. Use the indexing module for the full breakdown."},"IndexingPropertiesListPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/IndexingPropertyPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"stats_available":{"type":"boolean","title":"Stats Available","description":"False when the trailing-24h ClickHouse enrichment query failed; inspections_24h/unique_urls_24h are then 0 rather than a real count."}},"type":"object","required":["items","domain_id","stats_available"],"title":"IndexingPropertiesListPublic"},"IndexingPropertyEnrollResponse":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"property_id":{"type":"string","format":"uuid","title":"Property Id"},"property_url":{"type":"string","title":"Property Url"},"initial_inspection_triggered":{"type":"boolean","title":"Initial Inspection Triggered"},"initial_inspection_message":{"type":"string","title":"Initial Inspection Message"}},"type":"object","required":["domain_id","property_id","property_url","initial_inspection_triggered","initial_inspection_message"],"title":"IndexingPropertyEnrollResponse"},"IndexingPropertyPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"google_account_id":{"type":"string","format":"uuid","title":"Google Account Id"},"property_url":{"type":"string","title":"Property Url"},"property_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Property Type"},"permission_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Permission Level"},"linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Linked At"},"inspections_24h":{"type":"integer","title":"Inspections 24H"},"unique_urls_24h":{"type":"integer","title":"Unique Urls 24H"}},"type":"object","required":["id","google_account_id","property_url","inspections_24h","unique_urls_24h"],"title":"IndexingPropertyPublic"},"IndexingStatsPublic":{"properties":{"statuses":{"$ref":"#/components/schemas/IndexingStatusCountsPublic"},"comparison":{"anyOf":[{"$ref":"#/components/schemas/IndexingStatusCountsPublic"},{"type":"null"}]}},"type":"object","required":["statuses"],"title":"IndexingStatsPublic"},"IndexingStatusCountsPublic":{"properties":{"indexed":{"type":"integer","title":"Indexed","default":0},"crawled_not_indexed":{"type":"integer","title":"Crawled Not Indexed","default":0},"discovered_not_indexed":{"type":"integer","title":"Discovered Not Indexed","default":0},"excluded_noindex":{"type":"integer","title":"Excluded Noindex","default":0},"duplicate_canonical":{"type":"integer","title":"Duplicate Canonical","default":0},"redirect":{"type":"integer","title":"Redirect","default":0},"not_found":{"type":"integer","title":"Not Found","default":0},"server_error":{"type":"integer","title":"Server Error","default":0},"blocked_robots":{"type":"integer","title":"Blocked Robots","default":0},"unknown":{"type":"integer","title":"Unknown","default":0},"other":{"type":"integer","title":"Other","default":0},"total":{"type":"integer","title":"Total","default":0},"sitemap_urls":{"type":"integer","title":"Sitemap Urls","description":"Discovered sitemap URLs (Postgres), not GSC.","default":0}},"type":"object","title":"IndexingStatusCountsPublic"},"IndexingTimelinePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"gsc_linked":{"type":"boolean","title":"Gsc Linked"},"coverage_states":{"items":{"type":"string"},"type":"array","title":"Coverage States","description":"Every coverage_state observed in the window."},"timeline":{"items":{"$ref":"#/components/schemas/app__schemas__public__indexing__TimelinePoint"},"type":"array","title":"Timeline"}},"type":"object","required":["domain_id","gsc_linked","coverage_states","timeline"],"title":"IndexingTimelinePublic"},"InspectionEntry":{"properties":{"property_id":{"type":"string","format":"uuid","title":"Property Id"},"url":{"type":"string","title":"Url"},"inspected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Inspected At"},"index_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Index Verdict"},"coverage_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage State"},"robots_txt_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Robots Txt State"},"indexing_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Indexing State"},"page_fetch_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Page Fetch State"},"last_crawl_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Crawl Time"},"google_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Google Canonical"},"user_canonical":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Canonical"},"rich_results_verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rich Results Verdict"},"rich_results_issues":{"items":{"type":"string"},"type":"array","title":"Rich Results Issues"},"inspection_result_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inspection Result Link","description":"Deep link to Google's URL Inspection tool for this URL."}},"type":"object","required":["property_id","url"],"title":"InspectionEntry","description":"One URL's most recent GSC URL Inspection result."},"InspectionStats":{"properties":{"total_inspections":{"type":"integer","title":"Total Inspections"},"unique_urls_inspected":{"type":"integer","title":"Unique Urls Inspected"},"first_inspection":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Inspection"},"last_inspection":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Inspection"}},"type":"object","required":["total_inspections","unique_urls_inspected"],"title":"InspectionStats"},"InspectorCheckResultPublic":{"properties":{"check_type":{"type":"string","enum":["crux","uptime","ssl","whois","robots","chrome"],"title":"Check Type"},"status":{"type":"string","enum":["pending","success","no_data","failed"],"title":"Status"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"Raw check payload; shape varies by check_type. Absent when the check has not completed successfully."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["check_type","status"],"title":"InspectorCheckResultPublic","description":"One check (crux, uptime, ssl, whois, robots, chrome) run as part of a test."},"InspectorIssuePublic":{"properties":{"id":{"type":"string","title":"Id"},"severity":{"type":"string","enum":["critical","warning","info"],"title":"Severity"},"category":{"type":"string","enum":["performance","availability","security","seo","infrastructure","domain"],"title":"Category"},"title":{"type":"string","title":"Title"},"description":{"type":"string","title":"Description"},"recommendation":{"type":"string","title":"Recommendation"},"evidence":{"additionalProperties":true,"type":"object","title":"Evidence"},"sources":{"items":{"type":"string","enum":["crux","uptime","ssl","whois","robots","chrome"]},"type":"array","title":"Sources","description":"Which checks contributed evidence to this issue."},"score_impact":{"type":"integer","title":"Score Impact","description":"Points this issue deducted from the 100-point score.","default":0},"estimated_savings":{"anyOf":[{"$ref":"#/components/schemas/IssueSavingsPublic"},{"type":"null"}]},"pillar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pillar"},"sub_pillar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sub Pillar"},"is_fatal":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Fatal","description":"Whether this issue triggered the fatal cross-pillar score penalty."}},"type":"object","required":["id","severity","category","title","description","recommendation"],"title":"InspectorIssuePublic","description":"One actionable finding produced by the issues engine."},"InspectorIssuesSummaryPublic":{"properties":{"score":{"type":"integer","title":"Score","default":100},"grade":{"type":"string","enum":["A","B","C","D","F"],"title":"Grade","default":"A"},"critical":{"type":"integer","title":"Critical","default":0},"warning":{"type":"integer","title":"Warning","default":0},"info":{"type":"integer","title":"Info","default":0},"verdict":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict","description":"One-sentence headline summary of the report."},"verdict_detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verdict Detail","description":"1-2 paragraph narrative expanding on the verdict. Not always present."},"fatal_count":{"type":"integer","title":"Fatal Count","default":0},"pillars":{"additionalProperties":{"$ref":"#/components/schemas/PillarScorePublic"},"type":"object","title":"Pillars"},"sub_pillars":{"additionalProperties":{"$ref":"#/components/schemas/SubPillarScorePublic"},"type":"object","title":"Sub Pillars"}},"type":"object","title":"InspectorIssuesSummaryPublic","description":"The VitalSentinel Inspector Score \u2013 0-100 \u2013 with its pillar/sub-pillar breakdown."},"InspectorPreviousTestSummaryPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"issues_summary":{"$ref":"#/components/schemas/InspectorIssuesSummaryPublic"}},"type":"object","required":["id"],"title":"InspectorPreviousTestSummaryPublic","description":"Slim summary of the previous completed inspection for the same URL and device mode.\n\nUsed to compute score deltas without fetching the full previous test detail."},"InspectorQuotaPublic":{"properties":{"used":{"type":"integer","title":"Used"},"limit":{"type":"integer","title":"Limit"},"remaining":{"type":"integer","title":"Remaining"},"cycle_start":{"type":"string","format":"date","title":"Cycle Start"},"cycle_end":{"type":"string","format":"date","title":"Cycle End"}},"type":"object","required":["used","limit","remaining","cycle_start","cycle_end"],"title":"InspectorQuotaPublic","description":"Monthly Inspector run allowance for the workspace's current billing cycle."},"InspectorRunRequestPublic":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"Absolute URL to inspect, e.g. 'https://example.com/pricing'."},"device_mode":{"type":"string","enum":["mobile","desktop"],"title":"Device Mode","description":"Which device profile to emulate for the Chrome-based checks.","default":"mobile"}},"type":"object","required":["url"],"title":"InspectorRunRequestPublic","description":"Body of ``POST /workspaces/{workspace_id}/inspector/run``."},"InspectorTestDetailPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"url":{"type":"string","title":"Url"},"normalized_host":{"type":"string","title":"Normalized Host"},"device_mode":{"type":"string","enum":["mobile","desktop"],"title":"Device Mode","default":"mobile"},"status":{"type":"string","enum":["queued","running","completed","failed"],"title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"issues_summary":{"$ref":"#/components/schemas/InspectorIssuesSummaryPublic"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"checks":{"items":{"$ref":"#/components/schemas/InspectorCheckResultPublic"},"type":"array","title":"Checks"},"issues":{"items":{"$ref":"#/components/schemas/InspectorIssuePublic"},"type":"array","title":"Issues"},"queue_position":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Queue Position","description":"Position in the global FIFO run queue when status is 'queued'. 0 means next up. Absent once the test leaves the queued state."}},"type":"object","required":["id","url","normalized_host","status","created_at"],"title":"InspectorTestDetailPublic","description":"Full detail for one Inspector test: every check result and every issue found."},"InspectorTestSummaryPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"url":{"type":"string","title":"Url"},"normalized_host":{"type":"string","title":"Normalized Host"},"device_mode":{"type":"string","enum":["mobile","desktop"],"title":"Device Mode","default":"mobile"},"status":{"type":"string","enum":["queued","running","completed","failed"],"title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"issues_summary":{"$ref":"#/components/schemas/InspectorIssuesSummaryPublic"}},"type":"object","required":["id","url","normalized_host","status","created_at"],"title":"InspectorTestSummaryPublic","description":"One row in the Inspector test history \u2013 no per-check detail."},"IssueBucket":{"properties":{"count":{"type":"integer","title":"Count"},"examples":{"items":{"type":"string"},"type":"array","title":"Examples","description":"Up to 3 example URLs in this bucket."}},"type":"object","required":["count","examples"],"title":"IssueBucket"},"IssueSavingsPublic":{"properties":{"bytes_saved":{"type":"integer","title":"Bytes Saved","default":0},"ms_saved":{"type":"number","title":"Ms Saved","default":0},"requests_saved":{"type":"integer","title":"Requests Saved","default":0}},"type":"object","title":"IssueSavingsPublic","description":"Estimated real-world impact of fixing one issue, where the rule can compute it."},"IssueSummaryPublic":{"properties":{"coverage":{"$ref":"#/components/schemas/CoverageSummary"},"crawled_not_indexed":{"$ref":"#/components/schemas/IssueBucket"},"discovered_not_indexed":{"$ref":"#/components/schemas/IssueBucket"},"canonical_conflict":{"$ref":"#/components/schemas/IssueBucket"},"rich_results_fail":{"$ref":"#/components/schemas/IssueBucket"},"stale_crawl_30":{"$ref":"#/components/schemas/IssueBucket"},"stale_crawl_60":{"$ref":"#/components/schemas/IssueBucket"},"stale_crawl_90":{"$ref":"#/components/schemas/IssueBucket"},"noindex":{"$ref":"#/components/schemas/IssueBucket"},"page_with_redirect":{"$ref":"#/components/schemas/IssueBucket"},"robots":{"$ref":"#/components/schemas/RobotsIssueBucket"}},"type":"object","required":["crawled_not_indexed","discovered_not_indexed","canonical_conflict","rich_results_fail","stale_crawl_30","stale_crawl_60","stale_crawl_90","noindex","page_with_redirect","robots"],"title":"IssueSummaryPublic"},"JobAccepted":{"properties":{"job_id":{"type":"string","format":"uuid","title":"Job Id"},"status":{"type":"string","title":"Status","description":"'queued', 'running', 'completed', or 'failed'"},"poll_url":{"type":"string","title":"Poll Url","description":"Absolute path to poll for completion."},"quota_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Remaining"},"quota_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Quota Limit"},"quota_resets_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quota Resets On"}},"type":"object","required":["job_id","status","poll_url"],"title":"JobAccepted","description":"The 202 body for every asynchronous ``run:``-class operation."},"JobStatus":{"properties":{"job_id":{"type":"string","format":"uuid","title":"Job Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["job_id","status","created_at"],"title":"JobStatus"},"LCPElementTypeItemPublic":{"properties":{"element_type":{"type":"string","title":"Element Type"},"count":{"type":"integer","title":"Count"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"}},"type":"object","required":["element_type","count"],"title":"LCPElementTypeItemPublic"},"LCPElementTypesPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LCPElementTypeItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"LCPElementTypesPageResponse"},"LCPElementTypesTimelineItemPublic":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"element_type":{"type":"string","title":"Element Type"},"count":{"type":"integer","title":"Count"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"}},"type":"object","required":["element_type","count"],"title":"LCPElementTypesTimelineItemPublic"},"LCPElementTypesTimelinePageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LCPElementTypesTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"LCPElementTypesTimelinePageResponse"},"LCPResourceItemPublic":{"properties":{"resource_url":{"type":"string","title":"Resource Url"},"domain":{"type":"string","title":"Domain"},"count":{"type":"integer","title":"Count"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"}},"type":"object","required":["resource_url","domain","count"],"title":"LCPResourceItemPublic"},"LCPResourcesPageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LCPResourceItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"LCPResourcesPageResponse"},"LCPSubpartsPublic":{"properties":{"avg_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Ttfb Ms"},"avg_resource_load_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Resource Load Delay Ms"},"avg_resource_load_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Resource Load Time Ms"},"avg_element_render_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Element Render Delay Ms"}},"type":"object","title":"LCPSubpartsPublic"},"LCPSubpartsPublicResponse":{"properties":{"subparts":{"$ref":"#/components/schemas/LCPSubpartsPublic"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["subparts","window_start","window_end"],"title":"LCPSubpartsPublicResponse"},"LCPSubpartsTimelineItemPublic":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"avg_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Ttfb Ms"},"avg_resource_load_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Resource Load Delay Ms"},"avg_resource_load_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Resource Load Time Ms"},"avg_element_render_delay_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Element Render Delay Ms"}},"type":"object","title":"LCPSubpartsTimelineItemPublic"},"LCPSubpartsTimelinePageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LCPSubpartsTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"LCPSubpartsTimelinePageResponse"},"LanguageMetrics":{"properties":{"language":{"type":"string","title":"Language"},"language_name":{"type":"string","title":"Language Name"},"pageviews":{"type":"integer","title":"Pageviews","default":0},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["language","language_name"],"title":"LanguageMetrics"},"LinkMetrics":{"properties":{"url":{"type":"string","title":"Url"},"domain":{"type":"string","title":"Domain","description":"The link's host, derived from `url`. Before 2026-08-05 this was the empty string on every `/outbound` row, and on `/downloads` it carried the file name rather than a host."},"filename":{"type":"string","title":"Filename","description":"`/downloads` only: the file name. Empty on `/outbound`. Previously published in the `domain` field.","default":""},"clicks":{"type":"integer","title":"Clicks","default":0},"unique_visitors":{"type":"integer","title":"Unique Visitors","default":0},"page_visitors":{"type":"integer","title":"Page Visitors","description":"Unique visitors of the pages this link was clicked on.","default":0}},"type":"object","required":["url","domain"],"title":"LinkMetrics","description":"One outbound URL or downloaded file's click counts."},"LinksPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LinkMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"total_count":{"type":"integer","title":"Total Count","default":0}},"type":"object","required":["items"],"title":"LinksPublic","description":"Response for both ``/outbound`` and ``/downloads`` - identical shape, different source event type."},"MarkReadRequestPublic":{"properties":{"notification_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Notification Ids","description":"Notifications to mark read. Omit to mark every unread notification read."}},"type":"object","title":"MarkReadRequestPublic"},"MarkReadResponsePublic":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"marked_count":{"type":"integer","title":"Marked Count"}},"type":"object","required":["marked_count"],"title":"MarkReadResponsePublic"},"MediumMetrics":{"properties":{"medium":{"type":"string","title":"Medium"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["medium"],"title":"MediumMetrics"},"MemberCredits":{"properties":{"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit","description":"Cap the workspace owner set on this member for the cycle. Null means uncapped, which is the default. 0 means this member is blocked from the API and MCP while the rest of the workspace keeps working."},"used":{"type":"integer","title":"Used","description":"Credits this member has spent in the current cycle."},"remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Remaining","description":"Credits left under this member's own cap, or null when uncapped. A cap is not a grant: the workspace balance is still the outer bound, so the spendable amount is whichever of the two is smaller."}},"type":"object","required":["used"],"title":"MemberCredits","description":"The calling member's own share of the workspace allowance.\n\nReturned on every balance response, capped or not, so a member can tell \"the\nworkspace is out\" from \"I am capped\" without asking anyone."},"MetricChange_float_":{"properties":{"current":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current"},"previous":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Previous","description":"The same metric over the immediately preceding period."},"change":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Change","description":"current - previous."},"change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Change Pct","description":"Percentage movement relative to 'previous', rounded to 2 decimals. Null when 'previous' is zero, since the relative change is undefined there."}},"type":"object","title":"MetricChange[float]"},"MetricChange_int_":{"properties":{"current":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Current"},"previous":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Previous","description":"The same metric over the immediately preceding period."},"change":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Change","description":"current - previous."},"change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Change Pct","description":"Percentage movement relative to 'previous', rounded to 2 decimals. Null when 'previous' is zero, since the relative change is undefined there."}},"type":"object","title":"MetricChange[int]"},"NotificationMode":{"type":"string","enum":["instant"],"title":"NotificationMode","description":"Notification delivery modes"},"NotificationPreferencesPublic":{"properties":{"email_enabled":{"type":"boolean","title":"Email Enabled"},"email_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Address"},"notification_mode":{"$ref":"#/components/schemas/NotificationMode"},"min_severity":{"$ref":"#/components/schemas/AlertSeverity","description":"Minimum alert severity that triggers an email."},"in_app_enabled":{"type":"boolean","title":"In App Enabled"},"in_app_sound":{"type":"boolean","title":"In App Sound"},"browser_push_enabled":{"type":"boolean","title":"Browser Push Enabled"},"notify_uptime":{"type":"boolean","title":"Notify Uptime"},"notify_rum":{"type":"boolean","title":"Notify Rum"},"notify_crux":{"type":"boolean","title":"Notify Crux"},"notify_synthetic":{"type":"boolean","title":"Notify Synthetic"},"notify_robots_txt":{"type":"boolean","title":"Notify Robots Txt"},"notify_indexing":{"type":"boolean","title":"Notify Indexing"},"notify_gsc":{"type":"boolean","title":"Notify Gsc"},"notify_ga":{"type":"boolean","title":"Notify Ga"},"notify_web_analytics":{"type":"boolean","title":"Notify Web Analytics"},"email_weekly_domain_summary":{"type":"boolean","title":"Email Weekly Domain Summary","default":true},"email_payment_success":{"type":"boolean","title":"Email Payment Success","default":true},"email_payment_failed":{"type":"boolean","title":"Email Payment Failed","default":true},"email_subscription_renewed":{"type":"boolean","title":"Email Subscription Renewed","default":true},"email_subscription_canceled":{"type":"boolean","title":"Email Subscription Canceled","default":true},"email_plan_changed":{"type":"boolean","title":"Email Plan Changed","default":true},"email_invoice_ready":{"type":"boolean","title":"Email Invoice Ready","default":true},"email_upcoming_charge":{"type":"boolean","title":"Email Upcoming Charge","default":true},"email_domain_added":{"type":"boolean","title":"Email Domain Added","default":true},"email_domain_verification_pending":{"type":"boolean","title":"Email Domain Verification Pending","default":true},"email_extended_downtime":{"type":"boolean","title":"Email Extended Downtime","default":true},"email_downtime_resolved":{"type":"boolean","title":"Email Downtime Resolved","default":true},"email_member_joined":{"type":"boolean","title":"Email Member Joined","default":true},"email_member_removed":{"type":"boolean","title":"Email Member Removed","default":true},"email_role_changed":{"type":"boolean","title":"Email Role Changed","default":true},"email_ticket_created":{"type":"boolean","title":"Email Ticket Created","default":false},"email_ticket_response":{"type":"boolean","title":"Email Ticket Response","default":false},"email_ticket_status_changed":{"type":"boolean","title":"Email Ticket Status Changed","default":false},"email_ticket_closed":{"type":"boolean","title":"Email Ticket Closed","default":false},"email_failed_login_attempts":{"type":"boolean","title":"Email Failed Login Attempts","default":true},"email_2fa_changed":{"type":"boolean","title":"Email 2Fa Changed","default":true},"email_api_key_changed":{"type":"boolean","title":"Email Api Key Changed","default":true}},"type":"object","required":["email_enabled","notification_mode","min_severity","in_app_enabled","in_app_sound","browser_push_enabled","notify_uptime","notify_rum","notify_crux","notify_synthetic","notify_robots_txt","notify_indexing","notify_gsc","notify_ga","notify_web_analytics"],"title":"NotificationPreferencesPublic"},"NotificationPreferencesUpdatePublic":{"properties":{"email_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Enabled"},"email_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Address"},"notification_mode":{"anyOf":[{"$ref":"#/components/schemas/NotificationMode"},{"type":"null"}]},"min_severity":{"anyOf":[{"$ref":"#/components/schemas/AlertSeverity"},{"type":"null"}]},"in_app_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In App Enabled"},"in_app_sound":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"In App Sound"},"browser_push_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Browser Push Enabled"},"notify_uptime":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Uptime"},"notify_rum":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Rum"},"notify_crux":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Crux"},"notify_synthetic":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Synthetic"},"notify_robots_txt":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Robots Txt"},"notify_indexing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Indexing"},"notify_gsc":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Gsc"},"notify_ga":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Ga"},"notify_web_analytics":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Web Analytics"},"email_weekly_domain_summary":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Weekly Domain Summary"},"email_payment_success":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Payment Success"},"email_payment_failed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Payment Failed"},"email_subscription_renewed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Subscription Renewed"},"email_subscription_canceled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Subscription Canceled"},"email_plan_changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Plan Changed"},"email_invoice_ready":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Invoice Ready"},"email_upcoming_charge":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Upcoming Charge"},"email_domain_added":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Domain Added"},"email_domain_verification_pending":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Domain Verification Pending"},"email_extended_downtime":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Extended Downtime"},"email_downtime_resolved":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Downtime Resolved"},"email_member_joined":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Member Joined"},"email_member_removed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Member Removed"},"email_role_changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Role Changed"},"email_ticket_created":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Ticket Created"},"email_ticket_response":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Ticket Response"},"email_ticket_status_changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Ticket Status Changed"},"email_ticket_closed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Ticket Closed"},"email_failed_login_attempts":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Failed Login Attempts"},"email_2fa_changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email 2Fa Changed"},"email_api_key_changed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Api Key Changed"}},"type":"object","title":"NotificationPreferencesUpdatePublic","description":"All fields optional \u2013 only the ones supplied are changed."},"NotificationPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"perex":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Perex","description":"Short summary shown in the notification list."},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"Full content shown in the detail view."},"type":{"$ref":"#/components/schemas/NotificationType"},"severity":{"$ref":"#/components/schemas/NotificationSeverity"},"is_read":{"type":"boolean","title":"Is Read"},"read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Read At"},"domain_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Domain Id"},"domain_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain Name"},"alert_rule_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Alert Rule Id","description":"Set when type='alert'. Fetch the rule via GET .../alerts/rules/{domain_id}/{id}."},"alert_history_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Alert History Id"},"action_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Action Url"},"template_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Template Params","description":"Structured alert metadata (module, metric, current_value, unit, device, occurred_at, ...) for alert-backed notifications."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","type","severity","is_read","created_at"],"title":"NotificationPublic","description":"One in-app notification. Alert-backed notifications carry alert_rule_id."},"NotificationSeverity":{"type":"string","enum":["info","warning","critical","success"],"title":"NotificationSeverity"},"NotificationStatsPublic":{"properties":{"total_notifications":{"type":"integer","title":"Total Notifications"},"unread_count":{"type":"integer","title":"Unread Count"},"by_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Type"},"by_severity":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Severity"},"recent_count_24h":{"type":"integer","title":"Recent Count 24H","description":"Notifications created in the last 24 hours."}},"type":"object","required":["total_notifications","unread_count","by_type","by_severity","recent_count_24h"],"title":"NotificationStatsPublic"},"NotificationTrendPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TrendPointPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Always null - the full series is returned in one response."},"has_more":{"type":"boolean","title":"Has More","description":"Always false - the full series is returned in one response.","default":false},"unit":{"type":"string","title":"Unit"},"metric_label":{"type":"string","title":"Metric Label"},"threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Threshold"},"higher_is_worse":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Higher Is Worse","description":"Which side of the threshold is bad, from the rule's operator."},"breach":{"anyOf":[{"$ref":"#/components/schemas/TrendBreachPublic"},{"type":"null"}]},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device"},"granularity":{"type":"string","title":"Granularity"},"module":{"type":"string","title":"Module"},"metric":{"type":"string","title":"Metric"}},"type":"object","required":["items","unit","metric_label","granularity","module","metric"],"title":"NotificationTrendPublic","description":"The live series behind a fired alert's graph, recomputed from current data."},"NotificationType":{"type":"string","enum":["alert","system","announcement","welcome","billing"],"title":"NotificationType"},"OAuthErrorResponse":{"properties":{"error":{"type":"string","title":"Error","description":"One of: invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope, invalid_target."},"error_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"},"error_uri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Uri"}},"type":"object","required":["error"],"title":"OAuthErrorResponse","description":"RFC 6749 \u00a75.2 error response.\n\nDocumented as a schema so it appears in the OpenAPI document, but note it is emitted\ndirectly rather than raised as a :class:`PublicAPIProblem`: the token and revocation\nendpoints owe OAuth clients this shape, not ``application/problem+json``."},"OSVersionMetrics":{"properties":{"os":{"type":"string","title":"Os"},"version":{"type":"string","title":"Version"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["os","version"],"title":"OSVersionMetrics"},"OverlappingRulePairPublic":{"properties":{"user_agent_group":{"type":"string","title":"User Agent Group"},"type":{"type":"string","title":"Type","description":"'duplicate', 'redundant', or 'contradiction'."},"detail":{"type":"string","title":"Detail"},"outer_rule":{"type":"string","title":"Outer Rule"},"inner_rule":{"type":"string","title":"Inner Rule"}},"type":"object","required":["user_agent_group","type","detail","outer_rule","inner_rule"],"title":"OverlappingRulePairPublic"},"OverlappingRulesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OverlappingRulePairPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"truncated_groups":{"items":{"$ref":"#/components/schemas/TruncatedUserAgentGroupPublic"},"type":"array","title":"Truncated Groups","description":"User-agent groups excluded from the analysis for exceeding the rule cap. Empty when nothing was skipped; when non-empty, 'items' is incomplete."}},"type":"object","required":["items"],"title":"OverlappingRulesPage","description":"Overlap findings, plus what the analyser could not look at.\n\nThe robots-txt-monitor service skips an entire user-agent group once it exceeds its\nper-group rule cap - not a partial scan, the whole group, so even an exact duplicate\ninside an oversized group yields no finding. Reporting only ``pairs`` would let a bare\n``has_more: false`` mean \"your robots.txt has no overlapping rules\" when the truth is\n\"we did not look\". For a correctness tool that is the worst way to be wrong.\n\n``has_more`` is true whenever anything was skipped, and ``next_cursor`` stays absent\nbecause there is no further page to fetch - the same honest-but-unpageable pair used\nfor the capped analytics endpoints. ``truncated_groups`` names what was dropped, which\nis the part a user can act on: split the group, or shorten it."},"OverlappingRulesQueryRequest":{"properties":{"content":{"anyOf":[{"type":"string","maxLength":1048576},{"type":"null"}],"title":"Content","description":"Candidate robots.txt content to lint. Omit to lint the stored content."}},"type":"object","title":"OverlappingRulesQueryRequest"},"OverviewComparisonPublic":{"properties":{"crux":{"anyOf":[{"$ref":"#/components/schemas/CruxComparisonPublic"},{"type":"null"}]},"gsc":{"anyOf":[{"$ref":"#/components/schemas/GSCComparisonPublic"},{"type":"null"}]},"ga":{"anyOf":[{"$ref":"#/components/schemas/GAComparisonPublic"},{"type":"null"}]},"analytics":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"OverviewComparisonPublic","description":"Previous-period values and movement, for the modules that have one.\n\nOnly four sub-blocks exist. See this module's docstring for why ``rum``,\n``synthetic``, ``uptime``, ``robots`` and ``indexing`` are absent rather than null:\nthe internal endpoints behind them return no historical figure at all, and inventing\none here would be a number the platform cannot stand behind."},"OverviewLinkedGaPropertyPublic":{"properties":{"property_id":{"type":"string","title":"Property Id"},"property_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Property Name"}},"type":"object","required":["property_id"],"title":"OverviewLinkedGaPropertyPublic","description":"Identifies the Google Analytics 4 property linked to this domain, when the window's\nfirst-party data is empty. See ``OverviewPublic.linked_ga_property`` for why this is\nonly ever populated in that one case."},"OverviewPageSummary":{"properties":{"path":{"type":"string","title":"Path"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"pageviews":{"type":"integer","title":"Pageviews","default":0},"unique_visitors":{"type":"integer","title":"Unique Visitors","default":0},"entries":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Entries","description":"Always null. Use GET /analytics/pages."},"exits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exits","description":"Always null. Use GET /analytics/pages."},"bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate","description":"Always null. Use GET /analytics/pages."}},"type":"object","required":["path"],"title":"OverviewPageSummary","description":"A ``OverviewPublic.top_pages`` row: what the overview query itself computes.\n\nThe overview computes pageviews and unique visitors and nothing else, so entries,\nexits and bounce rate are null here rather than 0. They were 0 - and documented as 0 -\nuntil a sweep read \"/\" as having 0 entries in the overview and 1,505 from\n``/analytics/pages`` for the same window. A documented 0 is still a number, and it\nreads as a measurement; null cannot be mistaken for one."},"OverviewPreviousPublic":{"properties":{"start_date":{"type":"string","title":"Start Date","description":"Preceding window start (YYYY-MM-DD), inclusive."},"end_date":{"type":"string","title":"End Date","description":"Preceding window end (YYYY-MM-DD), inclusive."},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"avg_session_duration_seconds":{"type":"number","title":"Avg Session Duration Seconds","default":0.0}},"type":"object","required":["start_date","end_date"],"title":"OverviewPreviousPublic","description":"The preceding window's own totals and exact dates. Present only when the request\nset ``compare_previous=true`` - every ``*_change_pct``/``bounce_rate_change_pp`` field\non ``OverviewPublic`` is present regardless, so a caller that only wants the delta\nrarely needs this block, and every value here except the exact dates is otherwise\nderivable from that pair (current, delta).\n\nThe one case where it is not derivable is the case worth asking for: a ``*_change_pct``\nof null means this window's baseline was zero, and this block is where a caller sees\nthat directly instead of inferring it."},"OverviewPublic":{"properties":{"visitors":{"type":"integer","title":"Visitors","default":0},"measurement_basis":{"type":"string","title":"Measurement Basis","description":"Constant. States what these figures count and how they relate to a linked Google Analytics property, so a caller reading only this response cannot present one pipeline's number as the whole truth.","default":"VitalSentinel Web Analytics and a linked Google Analytics 4 property count different populations, so their numbers will not match and must not be reconciled. The first-party script is cookieless and fires for every visitor; the GA4 tag is the customer's own and fires subject to their consent manager. Expect user counts to agree closely and pageviews, traffic channels and geography to diverge, sometimes several-fold - on one domain over one window, users agreed within 0.4 per cent while pageviews differed 2.5x and one country differed 73x. Neither source is wrong."},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that has data (YYYY-MM-DD), or null when the window holds none. Compare it with the window you asked for before quoting a total: a trailing window is measured against today, and collection can lag or have stopped, so part of it may hold nothing at all."},"linked_ga_property":{"anyOf":[{"$ref":"#/components/schemas/OverviewLinkedGaPropertyPublic"},{"type":"null"}],"description":"The Google Analytics 4 property linked to this domain, checked and populated ONLY when data_through is null - the one case where a caller most needs to know that a second measurement source may hold real figures for a window first-party analytics recorded as empty. Null in every other case, including when the window has first-party data: checking is skipped there as unneeded, so null does not mean 'not linked' unless data_through is also null. When present, GET /ga/timeline and GET /ga/dimensions hold that property's actual numbers for the same window."},"visitors_events":{"type":"integer","title":"Visitors Events","description":"Unique visitors observed on the events table. Denominator for event/outbound/search conversion rates.","default":0},"new_visitors":{"type":"integer","title":"New Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","description":"Fraction of sessions with exactly one pageview and no engagement, 0-1.","default":0.0},"avg_session_duration_seconds":{"type":"number","title":"Avg Session Duration Seconds","default":0.0},"pages_per_session":{"type":"number","title":"Pages Per Session","default":0.0},"previous":{"anyOf":[{"$ref":"#/components/schemas/OverviewPreviousPublic"},{"type":"null"}],"description":"The preceding window's own totals and exact start/end dates. Populated only when compare_previous=true."},"visitors_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Visitors Change Pct","description":"visitors vs. the preceding window of equal length, as a relative percent change (e.g. 12.5 for +12.5%). Present independent of compare_previous, but NULL when the preceding window recorded zero visitors: a percent change has no defined value against a zero baseline, and null is the signal that no baseline exists rather than that nothing changed. It used to report a hardcoded 100.0 there, so a domain whose tracking went live partway into the window read as 'traffic up 100%' - a fabricated number on exactly the response with the least evidence behind it. Set compare_previous=true to see the baseline itself in 'previous'."},"sessions_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sessions Change Pct","description":"Same convention as visitors_change_pct, for sessions - including null when the preceding window recorded zero sessions."},"pageviews_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pageviews Change Pct","description":"Same convention as visitors_change_pct, for pageviews - including null when the preceding window recorded zero pageviews."},"avg_session_duration_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Session Duration Change Pct","description":"Same convention as visitors_change_pct, for avg_session_duration_seconds - including null when the preceding window's average was zero (which, for a duration, means it recorded no sessions to average)."},"bounce_rate_change_pp":{"type":"number","title":"Bounce Rate Change Pp","description":"bounce_rate now minus bounce_rate in the preceding window, in percentage POINTS - not a relative percent change like the *_change_pct fields above. bounce_rate is itself a rate, and a relative change of a rate reads as points to anyone quoting it next to a bounce-rate percentage: a rate moving from 58.3% to 54.8% is -3.5 percentage points, not the -5.9 a naive (new-old)/old*100 would report. Positive means bounce rate rose (worse).","default":0.0},"top_pages":{"items":{"$ref":"#/components/schemas/OverviewPageSummary"},"type":"array","title":"Top Pages","description":"Top 5 pages by pageviews. Pageviews and visitors only - see the shape."},"top_sources":{"items":{"$ref":"#/components/schemas/OverviewSourceSummary"},"type":"array","title":"Top Sources","description":"Top 5 traffic sources by visitors. Visitors and sessions only."},"top_countries":{"items":{"$ref":"#/components/schemas/GeoMetrics"},"type":"array","title":"Top Countries","description":"Top 5 countries by visitors."},"comparison_coverage":{"anyOf":[{"$ref":"#/components/schemas/ComparisonCoveragePublic"},{"type":"null"}],"description":"Whether the two windows behind every *_change_pct / *_change_pp field hold comparable amounts of data. Present only when compare_previous ran. Read like_for_like before quoting any change figure: the current window runs to now while ingestion runs to data_through, so a part-empty current side against a full baseline is the normal state, not the exception - it reported bounce rate up 28.0 percentage points where aligned months show 1.0."}},"type":"object","title":"OverviewPublic","description":"Key traffic metrics for the window, plus a comparison against the immediately\npreceding window of the same length. One field per compared metric, named\n``*_change_pct`` to match ``GET /rum/pages``/``/rum/overview``/``/gsc/*`` - except\n``bounce_rate_change_pp``, which reports percentage POINTS rather than a relative\npercent change (see that field for why). A ``*_change_pct`` field is null when the\npreceding window's own total for that metric was zero - a percent change against a zero\nbaseline is undefined, and a number there would be invented. Pass\n``compare_previous=true`` to also get the preceding window's own totals and exact dates\nin ``previous``."},"OverviewSourceSummary":{"properties":{"source":{"type":"string","title":"Source"},"medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Medium","description":"Always null. Use GET /analytics/sources."},"campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign","description":"Always null. Use GET /analytics/sources."},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pageviews","description":"Always null. Use GET /analytics/sources."},"bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate","description":"Always null. Use GET /analytics/sources."},"avg_session_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Session Duration Seconds","description":"Always null. Use GET /analytics/sources."}},"type":"object","required":["source"],"title":"OverviewSourceSummary","description":"A ``OverviewPublic.top_sources`` row.\n\n``source`` is the same UTM-source-falling-back-to-channel key ``GET /analytics/sources``'\n``by_source`` groups on, summed across the medium/campaign variants ``by_source`` also\ncarries - this row has no field to show them in, so a caller cannot see them anyway, and\nleaving them split would let one source appear as two same-named rows here. It used to\nbe the *channel* key instead (``by_channel``'s dimension): \"direct\" read 32,959 visitors\nhere against 32,860 from ``by_source`` for the identical window, a different dimension\nsilently standing in for the one this field is named after and the one the overview's\nown description points callers to. See the other null fields below for why they are\nnull rather than 0 - same reasoning as ``OverviewPageSummary``."},"PageFailureRollupPublic":{"properties":{"total_failing_pages":{"type":"integer","title":"Total Failing Pages","description":"Pages matching failed_only, over the whole domain for this window/filters - the count the returned rows are drawn from. Includes pages that fail a metric outright and pages with no sample for one; see *_no_sample_pages."},"lcp_failing_pages":{"type":"integer","title":"Lcp Failing Pages","description":"Pages that HAVE an LCP sample and whose average LCP is > 2500ms."},"cls_failing_pages":{"type":"integer","title":"Cls Failing Pages","description":"Pages that HAVE a CLS sample and whose average CLS is > 0.1."},"inp_failing_pages":{"type":"integer","title":"Inp Failing Pages","description":"Pages that HAVE an INP sample and whose average INP is > 200ms."},"lcp_no_sample_pages":{"type":"integer","title":"Lcp No Sample Pages","description":"Pages with no LCP sample at all in this window. Matched by failed_only and counted in total_failing_pages, but NOT in lcp_failing_pages - unmeasured is not the same finding as slow, and it is fixed differently."},"cls_no_sample_pages":{"type":"integer","title":"Cls No Sample Pages","description":"Pages with no CLS sample at all in this window. See lcp_no_sample_pages."},"inp_no_sample_pages":{"type":"integer","title":"Inp No Sample Pages","description":"Pages with no INP sample at all in this window - typically pages nobody interacted with, since INP only exists once a user interacts. Usually the largest of the three. See lcp_no_sample_pages."},"lcp_good_pages":{"type":"integer","title":"Lcp Good Pages","description":"Pages whose 75th-percentile LCP is <= 2500ms."},"lcp_needs_improvement_pages":{"type":"integer","title":"Lcp Needs Improvement Pages","description":"Pages whose 75th-percentile LCP is > 2500ms and <= 4000ms."},"lcp_poor_pages":{"type":"integer","title":"Lcp Poor Pages","description":"Pages whose 75th-percentile LCP is > 4000ms."},"cls_good_pages":{"type":"integer","title":"Cls Good Pages","description":"Pages whose 75th-percentile CLS is <= 0.1."},"cls_needs_improvement_pages":{"type":"integer","title":"Cls Needs Improvement Pages","description":"Pages whose 75th-percentile CLS is > 0.1 and <= 0.25."},"cls_poor_pages":{"type":"integer","title":"Cls Poor Pages","description":"Pages whose 75th-percentile CLS is > 0.25."},"inp_good_pages":{"type":"integer","title":"Inp Good Pages","description":"Pages whose 75th-percentile INP is <= 200ms."},"inp_needs_improvement_pages":{"type":"integer","title":"Inp Needs Improvement Pages","description":"Pages whose 75th-percentile INP is > 200ms and <= 500ms."},"inp_poor_pages":{"type":"integer","title":"Inp Poor Pages","description":"Pages whose 75th-percentile INP is > 500ms."}},"type":"object","required":["total_failing_pages","lcp_failing_pages","cls_failing_pages","inp_failing_pages","lcp_no_sample_pages","cls_no_sample_pages","inp_no_sample_pages","lcp_good_pages","lcp_needs_improvement_pages","lcp_poor_pages","cls_good_pages","cls_needs_improvement_pages","cls_poor_pages","inp_good_pages","inp_needs_improvement_pages","inp_poor_pages"],"title":"PageFailureRollupPublic","description":"Per-metric shape of the failed_only match, over the whole domain - not the page.\n\n``*_failing_pages`` decomposes failed_only's own OR condition one metric at a time, so\n\"how many fail on LCP vs CLS vs INP\" is one field lookup instead of paging every row.\nA page can fail more than one metric, so the three counts do not have to sum to\ntotal_failing_pages.\n\n``*_no_sample_pages`` is the other half of that decomposition, and the reason the two\nare separate. ``failed_only`` deliberately selects pages with no sample for a metric\nas well as pages measured over threshold - an unmeasured page is worth surfacing - but\ncounting both as \"failing\" made the number mean something a customer would misread. On\none real domain that reported 48,913 INP-failing pages where only 565 carried an\nactual sub-good INP sample: the rest had never fired an interaction event.\n``*_failing_pages + *_no_sample_pages`` is every page this metric contributed to\n``total_failing_pages``.\n\n``*_good_pages``/``*_needs_improvement_pages``/``*_poor_pages`` are a second, finer\ncut: the same Core Web Vitals good/needs-improvement/poor band used by\ncls_status/lcp_status, applied to each page's 75th-percentile value. Pages with no\nsample for a metric count toward neither triple.\n\n**The two cuts use different statistics and deliberately do not add up.**\n``*_failing_pages`` decomposes ``failed_only``'s own criterion, which thresholds the\nwindow MEAN, because that is what selects the rows. The good/needs-improvement/poor\ntriple bands the 75th PERCENTILE, because that is the statistic Google's thresholds are\ndefined on and what ``cls_status``/``lcp_status`` filter by. So\n``*_needs_improvement_pages + *_poor_pages`` lands near ``*_failing_pages`` but not on\nit - roughly 9-12 per cent apart on a real domain - and neither is wrong. Do not\npresent one as a check on the other; ``*_failing_pages`` answers \"how many rows did\nthis metric contribute\", the triple answers \"how bad is this metric across the site\"."},"PageMetrics":{"properties":{"path":{"type":"string","title":"Path"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"pageviews":{"type":"integer","title":"Pageviews","default":0},"unique_visitors":{"type":"integer","title":"Unique Visitors","default":0},"entries":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Entries","description":"Sessions that started on this page. **Not comparable across ``type``**, and the difference is not a rounding one: under type=all this is read from the sessions table's own recorded entry_page, while under type=entry it is derived independently from the first pageview of each session in the events table. On real data the two disagree in both directions - one page reads 45,221 the first way and 119,944 the second, another 23,403 against 18,381 - so choose one type and stay on it. Mixing them into a single ranking or dashboard tile silently corrupts it. Null under type=exit, which does not measure entries at all."},"exits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exits","description":"Sessions that ended on this page. Null under type=entry, which does not measure it - previously a hard 0, indistinguishable from 'nobody left here'."},"bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate","description":"Fraction of entry sessions on this page that bounced, 0-1. Only measured under type=all. Null under type=entry/type=exit, which do not compute it."}},"type":"object","required":["path"],"title":"PageMetrics","description":"One page's traffic, entry, and exit metrics, as returned by ``GET /analytics/pages``.\n\nA field this endpoint's chosen ``type`` does not measure is **null**, not 0. It used to\nbe 0, which is the trap ``OverviewPageSummary`` below already documents from the other\ndirection: a documented 0 is still a number and reads as a measurement. ``type=entry``\nreported ``bounce_rate: 0.0`` for every landing page - a perfect bounce rate, on the\none ranking a caller asks precisely so they can see which landing pages bounce."},"PagePerformancePageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PagePerformancePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"min_pageviews_applied":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Pageviews Applied","description":"Pageview floor actually applied before sort_by ranked the pages, or null when none was. Applied by default (20) whenever sort_by ranks by a percentile metric (lcp_p75, cls_p75, inp_p75, fcp_p75, ttfb_p75, or a *_regression key) and no url_filter narrows the request to a specific page - a 75th-percentile value from a single visit is a measurement artefact, not a worst-page finding. sort_by=pageviews/visitors and any url_filter lookup get no implicit floor: the traffic ranking already surfaces low-traffic pages honestly, and a URL lookup must never silently drop the very page asked for. Pass min_pageviews explicitly (1 disables the floor) to override."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"candidate_set_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Candidate Set Size","description":"Pages actually ranked, or null when the ranking was server-side over the whole domain (the default). Present only with compare_previous=true or a *_regression sort_by, which need both windows to cover the same pages and so rank within a bounded set of the busiest pages - capped at 500. Compare it against total, the domain-wide count of pages with traffic in this window: 109 against 27,689 means the rows returned are the worst of the busiest, not the worst on the site. has_more and next_cursor stop at the end of this set, because nothing beyond it is reachable in this mode at any cursor."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Plain-language caveat about how this result was ranked, or null when there is none. Populated with compare_previous=true or a *_regression sort_by, naming candidate_set_size against total. Quote it when reporting the numbers."},"failure_rollup":{"anyOf":[{"$ref":"#/components/schemas/PageFailureRollupPublic"},{"type":"null"}],"description":"Present only when failed_only=true. Per-metric counts computed over the WHOLE matched population (every failing page in the domain for this window and filters), not just the page of results returned - see PageFailureRollupPublic."}},"type":"object","required":["items","window_start","window_end"],"title":"PagePerformancePageResponse","description":"Cursor-paginated RUM page-performance list.\n\n``min_pageviews_applied`` mirrors ``GeographicPublicResponse.min_sessions_applied``: the\npageview floor actually applied before ``sort_by`` ranked the pages, so \"worst page for\nLCP\" states its own basis instead of depending on a floor the caller had to guess.\n\n``candidate_set_size`` and ``notice`` do the same job for the OTHER bound on this endpoint.\nIn ``compare_previous`` mode (implied by any ``*_regression`` sort_by) the ranking runs over\na bounded set of the busiest pages rather than the whole domain, and the ``sort_by``\ndescription used to point callers at a ``notice`` field that did not exist - so the caveat\nwas unreadable and a bounded top-N ranking was presented as the domain's worst regressions."},"PagePerformancePublic":{"properties":{"url":{"type":"string","title":"Url"},"page_views":{"type":"integer","title":"Page Views"},"unique_visitors":{"type":"integer","title":"Unique Visitors"},"avg_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Lcp Ms"},"avg_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Fcp Ms"},"avg_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Inp Ms"},"avg_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Cls"},"avg_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Ttfb Ms"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"},"p75_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Fcp Ms"},"p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Ms"},"p75_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Cls"},"p75_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Ttfb Ms"},"prev_p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Lcp Ms"},"prev_p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Inp Ms"},"prev_p75_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Cls"},"p75_lcp_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Change Pct"},"p75_inp_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Change Pct"},"p75_cls_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Cls Change Pct"},"lcp_good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Good Pct"},"lcp_needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Needs Improvement Pct"},"lcp_poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Poor Pct"},"inp_good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Good Pct"},"inp_needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Needs Improvement Pct"},"inp_poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Poor Pct"},"cls_good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls Good Pct"},"cls_needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls Needs Improvement Pct"},"cls_poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls Poor Pct"},"fcp_good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcp Good Pct"},"fcp_needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcp Needs Improvement Pct"},"fcp_poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcp Poor Pct"},"ttfb_good_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Good Pct"},"ttfb_needs_improvement_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Needs Improvement Pct"},"ttfb_poor_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Poor Pct"}},"type":"object","required":["url","page_views","unique_visitors"],"title":"PagePerformancePublic"},"Page_AlertHistoryPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AlertHistoryPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[AlertHistoryPublic]"},"Page_AlertRulePublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AlertRulePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[AlertRulePublic]"},"Page_AnnotationPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AnnotationPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[AnnotationPublic]"},"Page_CatalogSectionPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CatalogSectionPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[CatalogSectionPublic]"},"Page_CreditBalance_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CreditBalance"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[CreditBalance]"},"Page_CreditLogEntry_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CreditLogEntry"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[CreditLogEntry]"},"Page_CruxUrlPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CruxUrlPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[CruxUrlPublic]"},"Page_DomainEmailSubscriptionPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DomainEmailSubscriptionPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[DomainEmailSubscriptionPublic]"},"Page_DomainSummary_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DomainSummary"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[DomainSummary]"},"Page_ExperimentEvaluationPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExperimentEvaluationPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[ExperimentEvaluationPublic]"},"Page_ExperimentPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExperimentPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[ExperimentPublic]"},"Page_FunnelPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/FunnelPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[FunnelPublic]"},"Page_GAAccountPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GAAccountPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GAAccountPublic]"},"Page_GSCBrandPatternResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCBrandPatternResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GSCBrandPatternResponse]"},"Page_GSCExperimentResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCExperimentResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GSCExperimentResponse]"},"Page_GSCKnownEventResponse_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCKnownEventResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GSCKnownEventResponse]"},"Page_GSCRegisteredReport_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCRegisteredReport"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GSCRegisteredReport]"},"Page_GSCURLPattern_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/GSCURLPattern"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[GSCURLPattern]"},"Page_InspectionEntry_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InspectionEntry"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[InspectionEntry]"},"Page_InspectorTestSummaryPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InspectorTestSummaryPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[InspectorTestSummaryPublic]"},"Page_NotificationPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/NotificationPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[NotificationPublic]"},"Page_PriorityURLPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PriorityURLPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[PriorityURLPublic]"},"Page_ReportTemplatePublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ReportTemplatePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[ReportTemplatePublic]"},"Page_ResponseTimePointPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ResponseTimePointPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[ResponseTimePointPublic]"},"Page_RobotsTxtValidationPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RobotsTxtValidationPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[RobotsTxtValidationPublic]"},"Page_RobotsTxtVersionPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RobotsTxtVersionPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[RobotsTxtVersionPublic]"},"Page_SSLCertificatePublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SSLCertificatePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SSLCertificatePublic]"},"Page_SitemapConflictPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SitemapConflictPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SitemapConflictPublic]"},"Page_SitemapDiscoveredUrlPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SitemapDiscoveredUrlPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SitemapDiscoveredUrlPublic]"},"Page_StateChangeEntry_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/StateChangeEntry"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[StateChangeEntry]"},"Page_SyntheticAvailableLocation_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticAvailableLocation"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SyntheticAvailableLocation]"},"Page_SyntheticDomainLocationWithData_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticDomainLocationWithData"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SyntheticDomainLocationWithData]"},"Page_SyntheticMetricTrend_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticMetricTrend"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[SyntheticMetricTrend]"},"Page_TestURLResultPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TestURLResultPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[TestURLResultPublic]"},"Page_URLGroupPublic_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/URLGroupPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[URLGroupPublic]"},"Page_WorkspaceSummary_":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WorkspaceSummary"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"Page[WorkspaceSummary]"},"PagesPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PageMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that recorded a session (YYYY-MM-DD), or null when the window holds none. The same value GET /analytics/overview reports for the identical window, so a caller chaining one into the other is not told about a truncated window once and left to assume the second answer is complete. Check it before quoting a ranking as the window's ranking."}},"type":"object","required":["items"],"title":"PagesPublic","description":"Response for ``GET /pages``. Shape is identical regardless of ``type``.\n\nFor ``type=entry``/``type=exit`` only ``pageviews``/``unique_visitors``/``entries`` (or\n``exits``) are measured on each page; the fields belonging to the other variant come\nback null rather than 0, so a caller can tell \"not measured on this ranking\" from a\nreal zero - see ``PageMetrics``. ``total`` is the count of distinct pages matching the\nfilters, independent of limit/offset."},"PageviewFunnelStep":{"properties":{"type":{"type":"string","const":"pageview","title":"Type","default":"pageview"},"path_glob":{"type":"string","maxLength":500,"minLength":1,"title":"Path Glob"},"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"}},"type":"object","required":["path_glob"],"title":"PageviewFunnelStep"},"PillarScorePublic":{"properties":{"score":{"type":"number","title":"Score","default":0},"max":{"type":"number","title":"Max","default":0},"pct":{"type":"integer","title":"Pct","description":"Pillar score as a percentage of its max, 0-100.","default":100},"grade":{"type":"string","enum":["green","yellow","orange","red"],"title":"Grade","default":"green"}},"type":"object","title":"PillarScorePublic"},"PriorityURLCreateRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url","description":"Must be an http(s) URL whose host is this domain or a subdomain of it."}},"type":"object","required":["url"],"title":"PriorityURLCreateRequest"},"PriorityURLPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"url":{"type":"string","title":"Url"},"added_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Added By"},"added_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Added At"},"last_inspected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Inspected At"}},"type":"object","required":["id","domain_id","url"],"title":"PriorityURLPublic"},"RUMOverviewPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"period_days":{"type":"integer","title":"Period Days"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that has data (YYYY-MM-DD), or null when the window holds none. Check it against the window you asked for before quoting a total: the default window is a trailing 30 days measured against today, and where collection lags or has stopped, part of it holds nothing - on one domain that made the default report 78,631 sessions against 166,511 in the window that actually held data."},"total_events":{"type":"integer","title":"Total Events"},"total_sessions":{"type":"integer","title":"Total Sessions"},"total_errors":{"type":"integer","title":"Total Errors"},"avg_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Lcp Ms"},"avg_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Inp Ms"},"avg_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Cls"},"avg_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Fcp Ms"},"avg_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Ttfb Ms"},"p50_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Lcp Ms"},"p50_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Inp Ms"},"p50_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Cls"},"p50_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Fcp Ms"},"p50_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 Ttfb Ms"},"p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Ms"},"p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Ms"},"p75_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Cls"},"p75_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Fcp Ms"},"p75_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Ttfb Ms"},"p90_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Lcp Ms"},"p90_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Inp Ms"},"p90_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Cls"},"p90_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Fcp Ms"},"p90_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Ttfb Ms"},"p95_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Lcp Ms"},"p95_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Inp Ms"},"p95_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Cls"},"p95_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Fcp Ms"},"p95_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P95 Ttfb Ms"},"lcp_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Lcp Distribution","description":"Percentage of events that are good/needs_improvement/poor."},"inp_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Inp Distribution"},"cls_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Cls Distribution"},"fcp_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Fcp Distribution"},"ttfb_distribution":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Ttfb Distribution"},"device_breakdown":{"additionalProperties":{"type":"integer"},"type":"object","title":"Device Breakdown"},"browser_breakdown":{"additionalProperties":{"type":"integer"},"type":"object","title":"Browser Breakdown"},"prev_p75_lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Lcp Ms"},"prev_p75_inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Inp Ms"},"prev_p75_cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Cls"},"prev_p75_fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Fcp Ms"},"prev_p75_ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev P75 Ttfb Ms"},"p75_lcp_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Lcp Change Pct"},"p75_inp_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Inp Change Pct"},"p75_cls_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Cls Change Pct"},"p75_fcp_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Fcp Change Pct"},"p75_ttfb_change_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75 Ttfb Change Pct"},"comparison_coverage":{"anyOf":[{"$ref":"#/components/schemas/ComparisonCoveragePublic"},{"type":"null"}],"description":"Whether the two windows behind every *_change_pct field hold comparable amounts of data. Present only when compare_previous ran. Read like_for_like before quoting any change figure: the current window runs to now while ingestion runs to data_through, so a part-empty current side against a full baseline is the normal state, not the exception - it overstated a TTFB regression 6.5x before this field existed."}},"type":"object","required":["domain_id","period_days","window_start","window_end","total_events","total_sessions","total_errors","device_breakdown","browser_breakdown"],"title":"RUMOverviewPublic"},"RealtimeDevicePublic":{"properties":{"device_type":{"type":"string","title":"Device Type"},"visitors":{"type":"integer","title":"Visitors","default":0}},"type":"object","required":["device_type"],"title":"RealtimeDevicePublic"},"RealtimeEventPublic":{"properties":{"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count","default":0}},"type":"object","required":["name"],"title":"RealtimeEventPublic","description":"Free text: ``name`` comes from the customer's own site instrumentation."},"RealtimePublic":{"properties":{"active_visitors":{"type":"integer","title":"Active Visitors","default":0},"active_pages":{"items":{"$ref":"#/components/schemas/ActivePagePublic"},"type":"array","title":"Active Pages"},"visitors_by_country":{"items":{"$ref":"#/components/schemas/ActiveCountryPublic"},"type":"array","title":"Visitors By Country"},"recent_pageviews":{"items":{"$ref":"#/components/schemas/RecentPageviewPublic"},"type":"array","title":"Recent Pageviews"},"traffic_sources":{"items":{"$ref":"#/components/schemas/RealtimeTrafficSourcePublic"},"type":"array","title":"Traffic Sources"},"devices":{"items":{"$ref":"#/components/schemas/RealtimeDevicePublic"},"type":"array","title":"Devices"},"active_events":{"items":{"$ref":"#/components/schemas/RealtimeEventPublic"},"type":"array","title":"Active Events"},"top_referrers":{"items":{"$ref":"#/components/schemas/RealtimeReferrerPublic"},"type":"array","title":"Top Referrers"}},"type":"object","title":"RealtimePublic","description":"Snapshot of activity in roughly the last 5 minutes. Not cached - always live."},"RealtimeReferrerPublic":{"properties":{"domain":{"type":"string","title":"Domain"},"visitors":{"type":"integer","title":"Visitors","default":0}},"type":"object","required":["domain"],"title":"RealtimeReferrerPublic"},"RealtimeTrafficSourcePublic":{"properties":{"channel":{"type":"string","title":"Channel"},"visitors":{"type":"integer","title":"Visitors","default":0}},"type":"object","required":["channel"],"title":"RealtimeTrafficSourcePublic"},"RecentPageviewPublic":{"properties":{"path":{"type":"string","title":"Path"},"timestamp":{"type":"string","title":"Timestamp"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"device_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Type"}},"type":"object","required":["path","timestamp"],"title":"RecentPageviewPublic"},"ReferrerMetrics":{"properties":{"domain":{"type":"string","title":"Domain"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0}},"type":"object","required":["domain"],"title":"ReferrerMetrics"},"RegisteredReportPublic":{"properties":{"key":{"type":"string","title":"Key"},"tier":{"type":"string","enum":["fast","slow","ml"],"title":"Tier","description":"'fast' completes inline; 'slow'/'ml' are dispatched to a worker queue - poll the run for completion."},"freshness_seconds":{"type":"integer","title":"Freshness Seconds","description":"A run younger than this is served from cache instead of recomputed."}},"type":"object","required":["key","tier","freshness_seconds"],"title":"RegisteredReportPublic"},"RegisteredReportsPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RegisteredReportPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."}},"type":"object","required":["items"],"title":"RegisteredReportsPublic"},"ReportDataResponse":{"properties":{"template":{"additionalProperties":true,"type":"object","title":"Template"},"domain":{"additionalProperties":true,"type":"object","title":"Domain"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["template","domain","data"],"title":"ReportDataResponse","description":"Collected report data for UI rendering \u2013 the same payload the PDF is built from.\n\n``data`` is keyed by section id (e.g. 'rum', 'gsc', 'web_analytics'); each section's\nshape depends on which widgets that section has enabled and is intentionally left\nloosely typed here, since it mirrors nine independently evolving collector modules.\nCached for up to 8 hours per (template, date_range, compare_previous, sections_config)\ncombination \u2013 identical to the dashboard's own cache, so repeated polls of the same\ntemplate are usually served without a new ClickHouse scan."},"ReportGenerateRequestPublic":{"properties":{"date_range":{"anyOf":[{"type":"string","enum":["7d","14d","28d","3m","6m","12m"]},{"type":"null"}],"title":"Date Range"},"compare_previous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Compare Previous"}},"type":"object","title":"ReportGenerateRequestPublic","description":"Overrides for the template's stored defaults. Both fields optional."},"ReportRowsPublic":{"properties":{"items":{"items":{},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"run_id":{"type":"string","format":"uuid","title":"Run Id"},"report_key":{"type":"string","title":"Report Key"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["items","run_id","report_key","offset","limit"],"title":"ReportRowsPublic","description":"Server-side paginated slice of a completed run's ``result.rows``, returned in\n``items``. Offset-based: the row set is a fixed snapshot from one run, not a\ncontinuously inserted table."},"ReportRunPublic":{"properties":{"run_id":{"type":"string","format":"uuid","title":"Run Id"},"report_key":{"type":"string","title":"Report Key"},"status":{"type":"string","enum":["pending","running","completed","failed"],"title":"Status"},"cached":{"type":"boolean","title":"Cached","description":"True when this run's result was already fresh and nothing was recomputed.","default":false},"params":{"additionalProperties":true,"type":"object","title":"Params"},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"result":{"anyOf":[{},{"type":"null"}],"title":"Result","description":"Present once status is 'completed'. Shape varies by report_key."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"requested_at":{"type":"string","format":"date-time","title":"Requested At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"}},"type":"object","required":["run_id","report_key","status","params","requested_at"],"title":"ReportRunPublic"},"ReportRunRequest":{"properties":{"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Report-specific parameters, e.g. period_days."},"force":{"type":"boolean","title":"Force","description":"Bypass the freshness cache and recompute even if a fresh run already exists.","default":false}},"type":"object","title":"ReportRunRequest","description":"Body of ``POST /reports/{report_key}/run``. Both fields are optional; omit for a plain re-run."},"ReportSummaryPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ReportTilePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"}},"type":"object","required":["items","domain_id"],"title":"ReportSummaryPublic"},"ReportTemplateCreateRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"sections_config":{"items":{"$ref":"#/components/schemas/SectionConfigPublic"},"type":"array","maxItems":20,"minItems":1,"title":"Sections Config","description":"At least one section, no duplicate ids."},"default_date_range":{"type":"string","enum":["7d","14d","28d","3m","6m","12m"],"title":"Default Date Range","default":"28d"},"compare_previous":{"type":"boolean","title":"Compare Previous","default":true}},"type":"object","required":["name","sections_config"],"title":"ReportTemplateCreateRequest"},"ReportTemplatePublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"sections_config":{"items":{"$ref":"#/components/schemas/SectionConfigPublic"},"type":"array","title":"Sections Config"},"default_date_range":{"type":"string","title":"Default Date Range"},"compare_previous":{"type":"boolean","title":"Compare Previous"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","name","sections_config","default_date_range","compare_previous","created_at","updated_at"],"title":"ReportTemplatePublic"},"ReportTemplateUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"sections_config":{"anyOf":[{"items":{"$ref":"#/components/schemas/SectionConfigPublic"},"type":"array","maxItems":20,"minItems":1},{"type":"null"}],"title":"Sections Config"},"default_date_range":{"anyOf":[{"type":"string","enum":["7d","14d","28d","3m","6m","12m"]},{"type":"null"}],"title":"Default Date Range"},"compare_previous":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Compare Previous"}},"type":"object","title":"ReportTemplateUpdateRequest","description":"All fields optional; omitted fields leave the stored template unchanged."},"ReportTilePublic":{"properties":{"report_key":{"type":"string","title":"Report Key"},"has_run":{"type":"boolean","title":"Has Run"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count"},"preview":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Preview","description":"Small summary blob from the latest run, if any."}},"type":"object","required":["report_key","has_run"],"title":"ReportTilePublic"},"ResponseMeta":{"properties":{"range_start":{"type":"string","format":"date-time","title":"Range Start"},"range_end":{"type":"string","format":"date-time","title":"Range End"},"clamped_from":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Clamped From","description":"Present when the requested start predates the workspace's data retention. The response covers range_start onward, not the range you asked for."},"generated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generated At"},"cache_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cache Age Seconds"}},"type":"object","required":["range_start","range_end"],"title":"ResponseMeta","description":"Envelope metadata returned alongside range-scoped responses."},"ResponseTimePointPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"dns_time_ms":{"type":"integer","title":"Dns Time Ms"},"connect_time_ms":{"type":"integer","title":"Connect Time Ms"},"tls_time_ms":{"type":"integer","title":"Tls Time Ms"},"total_time_ms":{"type":"integer","title":"Total Time Ms"}},"type":"object","required":["domain_id","timestamp","dns_time_ms","connect_time_ms","tls_time_ms","total_time_ms"],"title":"ResponseTimePointPublic","description":"One check's DNS/connect/TLS/total timing breakdown.\n\nWrapped in the standard ``Page`` envelope with no start/end range alongside it: the\ninternal handler clamps the requested window internally and does not report the\nclamped bounds, so a wrapper field here could only echo the requested range, not the\none actually applied - misleading rather than useful."},"RevocationResult":{"properties":{"revoked":{"type":"boolean","title":"Revoked","default":true}},"type":"object","title":"RevocationResult","description":"Body for ``POST /oauth/revoke``.\n\nRFC 7009 \u00a72.2 says clients ignore the body entirely, and that an unknown token is\nstill a success - so this is always ``{\"revoked\": true}`` and never confirms whether\nthe token existed."},"RobotsIssueBucket":{"properties":{"total":{"type":"integer","title":"Total"},"by_state":{"items":{"$ref":"#/components/schemas/StateCount"},"type":"array","title":"By State"},"examples":{"items":{"type":"string"},"type":"array","title":"Examples"}},"type":"object","required":["total","by_state","examples"],"title":"RobotsIssueBucket"},"RobotsOverviewPublic":{"properties":{"has_errors":{"type":"boolean","title":"Has Errors","default":false},"error_count":{"type":"integer","title":"Error Count","default":0},"warning_count":{"type":"integer","title":"Warning Count","default":0},"last_checked":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Checked","description":"UTC ISO 8601 with a 'Z' suffix."},"last_changed":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Changed","description":"When the file's content last changed. UTC ISO 8601."},"content_length_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Content Length Bytes"},"is_blocked_403":{"type":"boolean","title":"Is Blocked 403","description":"The last fetch of robots.txt itself returned 403.","default":false}},"type":"object","title":"RobotsOverviewPublic"},"RobotsStatusPublic":{"properties":{"has_errors":{"type":"boolean","title":"Has Errors"},"error_count":{"type":"integer","title":"Error Count","default":0},"warning_count":{"type":"integer","title":"Warning Count","default":0},"last_changed":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Changed"}},"type":"object","required":["has_errors"],"title":"RobotsStatusPublic"},"RobotsTxtChangePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"changed_at":{"type":"string","format":"date-time","title":"Changed At"},"old_version_id":{"type":"string","format":"uuid","title":"Old Version Id"},"new_version_id":{"type":"string","format":"uuid","title":"New Version Id"},"change_type":{"type":"string","title":"Change Type"},"lines_added":{"type":"integer","title":"Lines Added"},"lines_removed":{"type":"integer","title":"Lines Removed"},"lines_modified":{"type":"integer","title":"Lines Modified"},"diff":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Diff","description":"Unified diff from the old version to the new one, present only when the request passes include_diff=true. Null when either version's content is no longer retained. Can also be the empty string - see content_unchanged, which distinguishes that from the null case. The line counts alone say how much changed but never what, so a caller asked 'what changed in robots.txt' could previously answer only '2 lines added, 1 removed'."},"content_unchanged":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Content Unchanged","description":"Set only when include_diff=true and both versions' content is still retained. True means the two stored versions are byte-for-byte identical even though this row is logged as a content_change - the fetch's content hash differed for a reason that produced no textual edit, so diff is a legitimate empty string, not missing data. False means the versions differ and diff holds the difference. Null when include_diff was not passed, or when diff is null because a version's content is no longer retained."}},"type":"object","required":["domain_id","changed_at","old_version_id","new_version_id","change_type","lines_added","lines_removed","lines_modified"],"title":"RobotsTxtChangePublic"},"RobotsTxtChangesPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RobotsTxtChangePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window searched - the default is 90 days back."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window searched."},"latest_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Change At","description":"When robots.txt last changed at ANY time, ignoring the window - populated only when this page came back empty, which is the case where it matters. Null there means no change has ever been recorded for this domain; a date means there is history, just not inside window_start..window_end, so widen the window with 'start' rather than reporting the file as never edited."}},"type":"object","required":["items","window_start","window_end"],"title":"RobotsTxtChangesPage","description":"Cursor-paginated robots.txt change history, with the window it actually covered.\n\nAn empty ``items`` used to read identically to \"robots.txt has never changed\": the\nendpoint defaults to the trailing 90 days, and on a domain whose last real edit was\n134 days ago that default returns nothing at all. ``window_start``/``window_end`` say\nwhat was searched and ``latest_change_at`` says whether anything exists outside it, so\n\"no changes recently\" and \"no changes ever\" are finally different answers."},"RobotsTxtFindingPublic":{"properties":{"code":{"type":"string","title":"Code"},"standard":{"type":"string","title":"Standard","description":"'rfc9309' or 'google'."},"severity":{"type":"string","title":"Severity","description":"'error', 'warning', or 'info'."},"line":{"type":"integer","title":"Line","description":"0 means a file-level finding.","default":0},"message":{"type":"string","title":"Message"},"docs_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Docs Url"}},"type":"object","required":["code","standard","severity","message"],"title":"RobotsTxtFindingPublic"},"RobotsTxtHealthPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"latest_fetched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Fetched At"},"http_status":{"type":"integer","title":"Http Status","default":0},"response_time_ms":{"type":"integer","title":"Response Time Ms","default":0},"size_bytes":{"type":"integer","title":"Size Bytes","default":0},"cache_control":{"type":"string","title":"Cache Control","default":""},"etag":{"type":"string","title":"Etag","default":""},"last_modified":{"type":"string","title":"Last Modified","default":""},"error":{"type":"string","title":"Error","default":""},"fetch_count_7d":{"type":"integer","title":"Fetch Count 7D","default":0},"failure_count_7d":{"type":"integer","title":"Failure Count 7D","default":0},"google_max_size_bytes":{"type":"integer","title":"Google Max Size Bytes","default":0}},"type":"object","required":["domain_id"],"title":"RobotsTxtHealthPublic"},"RobotsTxtValidationPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"validated_at":{"type":"string","format":"date-time","title":"Validated At"},"is_valid":{"type":"boolean","title":"Is Valid"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"findings":{"items":{"$ref":"#/components/schemas/RobotsTxtFindingPublic"},"type":"array","title":"Findings"}},"type":"object","required":["domain_id","validated_at","is_valid","errors","warnings"],"title":"RobotsTxtValidationPublic"},"RobotsTxtVersionPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"version_id":{"type":"string","format":"uuid","title":"Version Id"},"fetched_at":{"type":"string","format":"date-time","title":"Fetched At","description":"When THIS VERSION was first fetched - i.e. when the content last changed. A new version row is only written when the body differs from the previous one, so on a file that has not changed in months this is months old while the file is still being re-fetched many times a day. Read last_verified_at for 'when did you last check', which is the question this field's name invites and does not answer: a 74-byte robots.txt fetched 1,084 times, most recently today, reported fetched_at four months back, and a customer reading it alone concluded nobody had looked since."},"content_hash":{"type":"string","title":"Content Hash"},"content":{"type":"string","title":"Content"},"size_bytes":{"type":"integer","title":"Size Bytes"},"line_count":{"type":"integer","title":"Line Count"},"last_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Verified At","description":"Most recent successful fetch of this domain's robots.txt, whether or not the content changed - the freshness of the reading. Null when the fetch-health record is unavailable; that means unknown, not 'never fetched'."},"last_checked":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked","description":"When the VALIDATION findings below were last computed, which is neither the last fetch nor the last content change - it runs on its own schedule and can lag both. Use last_verified_at to judge whether the file itself is being monitored."},"has_errors":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Errors"},"errors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Errors"},"warnings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Warnings"},"findings":{"anyOf":[{"items":{"$ref":"#/components/schemas/RobotsTxtFindingPublic"},"type":"array"},{"type":"null"}],"title":"Findings"}},"type":"object","required":["domain_id","version_id","fetched_at","content_hash","content","size_bytes","line_count"],"title":"RobotsTxtVersionPublic"},"RumCoreMetricsPublic":{"properties":{"lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Ms"},"cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls","description":"Cumulative Layout Shift score (unitless)."},"inp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inp Ms"},"ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Ms"},"fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcp Ms"},"error_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Error Rate Pct"},"lcp_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lcp Samples","description":"Page loads behind lcp_ms. Not shared with the other metrics."},"cls_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cls Samples","description":"Page loads behind cls. Not shared with the other metrics."},"inp_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inp Samples","description":"Page loads behind inp_ms. Not shared with the other metrics."},"ttfb_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ttfb Samples","description":"Page loads behind ttfb_ms. Not shared with the other metrics."},"fcp_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fcp Samples","description":"Page loads behind fcp_ms. Not shared with the other metrics."}},"type":"object","title":"RumCoreMetricsPublic","description":"One device's RUM vitals.\n\nEvery metric here is a 75th percentile over the page loads that reported THAT metric,\nand those populations are not the same set of page loads. A slow load reports TTFB and\ncan be abandoned before LCP is ever finalised, so TTFB carries a slow tail that LCP\nnever sees. That makes a figure like ``ttfb_ms`` above ``lcp_ms`` ordinary and not a\ncontradiction, even though TTFB is a component of LCP within any single load. Read the\n``*_samples`` denominators before comparing two metrics on this object against each\nother, and never infer a breakdown of one metric from another."},"RumHasDataBulkPublic":{"properties":{"workspace_id":{"type":"string","format":"uuid","title":"Workspace Id"},"domains":{"items":{"$ref":"#/components/schemas/RumHasDataEntryPublic"},"type":"array","title":"Domains","description":"Every domain in workspace_id, each with its own has_data flag."}},"type":"object","required":["workspace_id","domains"],"title":"RumHasDataBulkPublic","description":"One grouped ClickHouse query, not domain_ids.length calls - see the route docstring."},"RumHasDataEntryPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"has_data":{"type":"boolean","title":"Has Data"}},"type":"object","required":["domain_id","has_data"],"title":"RumHasDataEntryPublic"},"RumMetricsComparisonPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/RumCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/RumCoreMetricsPublic"},{"type":"null"}]}},"type":"object","title":"RumMetricsComparisonPublic"},"RumMetricsPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/RumCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/RumCoreMetricsPublic"},{"type":"null"}]},"comparison":{"anyOf":[{"$ref":"#/components/schemas/RumMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"RumMetricsPublic"},"RumOverviewPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/DeviceVitalsPublic"},{"type":"null"}]}},"type":"object","title":"RumOverviewPublic","description":"Field data from this domain's own visitors, over the last 28 days.\n\nCLS is measured Above the Fold (the first measurement of each session), matching the\ndashboard's default."},"SSLCertificatePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"issuer":{"type":"string","title":"Issuer"},"common_name":{"type":"string","title":"Common Name"},"san_list":{"items":{"type":"string"},"type":"array","title":"San List"},"is_valid":{"type":"boolean","title":"Is Valid"},"days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiry"}},"type":"object","required":["domain_id","timestamp","expires_at","issuer","common_name","is_valid"],"title":"SSLCertificatePublic","description":"One observed certificate. Used for both a single point-in-time read (latest) and\neach row of the history list."},"SSLChainPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"scan_status":{"type":"string","enum":["scanned","never_scanned"],"title":"Scan Status","description":"'never_scanned' means no certificate chain scan has completed for this domain, so every chain-derived field below (chain_valid, HSTS, OCSP, issuer, ...) is null and none of them is a finding about the certificate. This is not an error state: GET .../ssl/summary reports the same condition as status 'unknown'. days_until_expiry is the one exception - see 'source'.","default":"scanned"},"source":{"anyOf":[{"type":"string","enum":["chain_scan","uptime_checker"]},{"type":"null"}],"title":"Source","description":"Where days_until_expiry came from - this field only, not a verdict on the whole row. 'chain_scan' means the chain scan itself produced this expiry date. 'uptime_checker' means the expiry date was recovered from the uptime monitor's independent observation instead; this happens in two cases: scan_status is 'never_scanned' (no chain scan has ever run, so every other field is null), or scan_status is 'scanned' but that scan could not determine an expiry date on its own (chain_valid, HSTS and OCSP are then still real scan data, not null). Null means neither store has an expiry date for this domain."},"chain_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Chain Valid","description":"Null when scan_status is 'never_scanned'. False means a scan ran and the chain failed validation - never conflate the two."},"chain_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chain Error"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issuer"},"serial_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serial Number"},"not_before":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Not Before"},"not_after":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Not After"},"signature_algorithm":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Signature Algorithm"},"key_size_bits":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Key Size Bits"},"san_list":{"items":{"type":"string"},"type":"array","title":"San List"},"has_sct":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Sct","description":"Certificate Transparency SCTs present."},"sct_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sct Count"},"ocsp_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocsp Status"},"ocsp_this_update":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ocsp This Update"},"ocsp_next_update":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ocsp Next Update"},"ocsp_stapling_supported":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ocsp Stapling Supported"},"is_revoked":{"type":"boolean","title":"Is Revoked","default":false},"revocation_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revocation Reason"},"revocation_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revocation Date"},"has_hsts":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Hsts"},"hsts_max_age_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Hsts Max Age Seconds"},"days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiry","description":"From the chain scan when source is 'chain_scan'; recovered from the uptime checker's own observed expiry when source is 'uptime_checker' - which can happen even when scan_status is 'scanned', if the scan itself could not determine an expiry date. Null when neither store has an answer."},"is_expiring_soon":{"type":"boolean","title":"Is Expiring Soon","description":"days_until_expiry <= 30, derived from whichever store that day count came from (see 'source') - not only from a chain scan's own expiry date. False when days_until_expiry is null, which means no expiry date is known rather than a certificate known to be healthy.","default":false},"is_expired":{"type":"boolean","title":"Is Expired","description":"days_until_expiry <= 0, derived from whichever store that day count came from (see 'source'). Recomputed against a day count recovered from the uptime checker, including when scan_status is 'never_scanned': it was left false there, so a certificate that had lapsed 10 days ago answered days_until_expiry -10 and is_expired false in the same payload. False when days_until_expiry is null - no expiry date is known, which is not the same as a certificate known to be current.","default":false},"validated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Validated At","description":"When this check last ran."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["domain_id"],"title":"SSLChainPublic","description":"Full certificate chain detail from the most recent scheduled check."},"SSLEventItemPublic":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"issuer":{"type":"string","title":"Issuer"},"common_name":{"type":"string","title":"Common Name"},"san_count":{"type":"integer","title":"San Count"},"event_type":{"type":"string","title":"Event Type","description":"'issued' | 'renewed' | 'issuer_changed' | 'sans_changed'"}},"type":"object","required":["timestamp","expires_at","issuer","common_name","san_count","event_type"],"title":"SSLEventItemPublic","description":"A single SSL change event, diffed from the certificate history."},"SSLEventsPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SSLEventItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"}},"type":"object","required":["items","domain_id"],"title":"SSLEventsPublic"},"SSLSummaryPublic":{"properties":{"status":{"type":"string","title":"Status","description":"'healthy' | 'warning' | 'critical' | 'unknown'"},"scan_status":{"type":"string","enum":["scanned","never_scanned"],"title":"Scan Status","description":"Whether a certificate chain scan has completed for this domain, which is a different question from `source` and must not be inferred from it. `source` names where days_until_expiry came from, one field; this names whether the scan ran at all. They diverge on a real and common shape: a scan that completed and populated chain_valid/has_hsts/ocsp_status but could not read its own expiry date reports source='uptime_checker' while scan_status is 'scanned'. Reading 'never scanned' off source there tells a customer their certificate has never been checked when it has - so use this field. Mirrors GET /{domain_id}/ssl's own scan_status, which has always carried it."},"message":{"type":"string","title":"Message"},"issues":{"items":{"type":"string"},"type":"array","title":"Issues"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"days_until_expiry":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Until Expiry","description":"From the chain scan when source is 'chain_scan'; recovered from the uptime checker's own observed expiry when source is 'uptime_checker'. That recovery happens whenever the chain scan itself has no expiry date, whatever status it reports - not only when status is 'unknown' (no chain scan has ever run). A completed scan is never reported 'healthy' while its own expiry is unknown. Null when neither store has an answer."},"chain_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Chain Valid","description":"Null when status is 'unknown', meaning no chain scan has run and validity is simply not known. False means a scan ran and the chain failed validation. Never read the two as the same thing: a `false` here used to be returned for every never-scanned domain, which reads as a broken certificate."},"has_hsts":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Hsts"},"ocsp_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocsp Status"},"source":{"anyOf":[{"type":"string","enum":["chain_scan","uptime_checker"]},{"type":"null"}],"title":"Source","description":"Where days_until_expiry came from - this field only, not a verdict on the rest of the row. 'chain_scan' means the chain scan itself produced this expiry date. 'uptime_checker' means the expiry date was recovered from the uptime monitor's independent observation instead; this happens in two cases: status is 'unknown' (no chain scan has ever run, so chain_valid, has_hsts and ocsp_status are all null), or a scan did run and reports a real status/chain_valid/has_hsts/ocsp_status but could not determine an expiry date of its own. Null means neither store has an expiry date for this domain."}},"type":"object","required":["status","scan_status","message"],"title":"SSLSummaryPublic","description":"Quick-glance SSL status - what a status widget needs, nothing more."},"ScreenSizeMetrics":{"properties":{"resolution":{"type":"string","title":"Resolution"},"visitors":{"type":"integer","title":"Visitors","default":0},"percentage":{"type":"number","title":"Percentage","default":0.0}},"type":"object","required":["resolution"],"title":"ScreenSizeMetrics"},"SearchPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SearchQueryMetrics"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"total_searches":{"type":"integer","title":"Total Searches","default":0},"total_count":{"type":"integer","title":"Total Count","default":0}},"type":"object","required":["items"],"title":"SearchPublic"},"SearchQueryMetrics":{"properties":{"query":{"type":"string","title":"Query"},"searches":{"type":"integer","title":"Searches","default":0},"unique_visitors":{"type":"integer","title":"Unique Visitors","default":0},"page_visitors":{"type":"integer","title":"Page Visitors","description":"Unique visitors of the pages this query was searched from.","default":0}},"type":"object","required":["query"],"title":"SearchQueryMetrics","description":"One site-search query's free text and counts. ``query`` is visitor-entered free text."},"SectionConfigPublic":{"properties":{"id":{"type":"string","title":"Id","description":"Section id, e.g. 'rum', 'gsc'. See GET /reports/sections-catalog."},"enabled":{"type":"boolean","title":"Enabled","default":true},"order":{"type":"integer","title":"Order","default":0},"title":{"type":"string","title":"Title"},"widgets":{"items":{"$ref":"#/components/schemas/WidgetConfigPublic"},"type":"array","maxItems":50,"title":"Widgets"}},"type":"object","required":["id","title"],"title":"SectionConfigPublic","description":"One report section: which widgets it shows and in what order."},"SimulateRequest":{"properties":{"candidate_content":{"type":"string","maxLength":1048576,"title":"Candidate Content"},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path"}},"type":"object","required":["candidate_content"],"title":"SimulateRequest"},"SimulateResponsePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"path_checked":{"type":"string","title":"Path Checked"},"is_valid":{"type":"boolean","title":"Is Valid"},"findings":{"items":{"$ref":"#/components/schemas/RobotsTxtFindingPublic"},"type":"array","title":"Findings"},"flips":{"items":{"$ref":"#/components/schemas/SimulateUAFlipPublic"},"type":"array","title":"Flips"},"categories":{"items":{"$ref":"#/components/schemas/CrawlerCategoryPublic"},"type":"array","title":"Categories"},"has_current":{"type":"boolean","title":"Has Current","description":"False when the domain has no stored robots.txt yet."}},"type":"object","required":["domain_id","path_checked","is_valid","findings","flips","categories","has_current"],"title":"SimulateResponsePublic"},"SimulateUAFlipPublic":{"properties":{"user_agent":{"type":"string","title":"User Agent"},"before_allowed":{"type":"boolean","title":"Before Allowed"},"after_allowed":{"type":"boolean","title":"After Allowed"},"before_rule":{"type":"string","title":"Before Rule"},"after_rule":{"type":"string","title":"After Rule"}},"type":"object","required":["user_agent","before_allowed","after_allowed","before_rule","after_rule"],"title":"SimulateUAFlipPublic"},"SitemapConflictPublic":{"properties":{"url":{"type":"string","title":"Url"},"disallow_pattern":{"type":"string","title":"Disallow Pattern"},"user_agent":{"type":"string","title":"User Agent"},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["url","disallow_pattern","user_agent","checked_at"],"title":"SitemapConflictPublic"},"SitemapConflictsSummaryPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"total_sitemap_urls":{"type":"integer","title":"Total Sitemap Urls","description":"Sitemap URLs that existed at the moment of the last conflict check - not necessarily now."},"blocked_urls_count":{"type":"integer","title":"Blocked Urls Count","description":"Blocked URLs as of that same check. See 'stale' before treating this as current."},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At","description":"None means no conflict check has ever run for this domain."},"robots_version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Robots Version Id"},"current_sitemap_url_count":{"type":"integer","title":"Current Sitemap Url Count","description":"Sitemap URLs stored right now, independent of when the conflict check last ran - a live read of the same discovered-URL store the last completed scan wrote to, not a fresh fetch of the site's sitemap. Compare against total_sitemap_urls to see whether the check has kept pace with that store; see scan_age_days for whether the store itself is current."},"scan_data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Scan Data Through","description":"Date of the most recent completed sitemap scan behind current_sitemap_url_count - the same value GET .../sitemap/stats calls data_through, named differently here only to avoid colliding with this response's own last_checked_at (the conflict check's timestamp, a different thing). Null when no scan has ever completed for this domain."},"scan_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scan Age Days","description":"Days between scan_data_through and today. current_sitemap_url_count matching total_sitemap_urls only means the check has kept pace with the stored sitemap - it says nothing about whether that stored sitemap reflects the live site, which is what this number answers. Null when no scan has ever completed."},"sitemap_lagging":{"type":"boolean","title":"Sitemap Lagging","description":"True when current_sitemap_url_count differs from total_sitemap_urls: the stored sitemap has changed since this check ran, so blocked_urls_count may undercount. This is the ONE cause POST .../sitemap-conflicts/recompute fixes - it re-diffs the stored sitemap against the current robots.txt.","default":false},"scan_stale":{"type":"boolean","title":"Scan Stale","description":"True when scan_age_days exceeds the freshness limit: the sitemap scan behind these counts is old, so even a check that exactly matches the stored sitemap may be checking data the live site no longer matches. Recompute CANNOT fix this - it never fetches a fresh sitemap. Only a new scan does (a dashboard-only action today, not one this API exposes).","default":false},"stale":{"type":"boolean","title":"Stale","description":"sitemap_lagging OR scan_stale - kept as the single 'do not quote blocked_urls_count' signal. Read the two fields above to decide what to do about it, since only one of them is fixable from this API. True for either of two independent reasons. (1) current_sitemap_url_count differs from total_sitemap_urls: the stored sitemap has changed since this check ran, so blocked_urls_count may undercount - POST .../sitemap-conflicts/recompute fixes this by re-diffing the stored sitemap against the current robots.txt. (2) scan_age_days is more than 7: the sitemap scan behind those counts is old, so even a check that exactly matches the stored sitemap may be checking data that no longer matches the live site - recompute CANNOT fix this, since it never fetches a fresh sitemap; only a new sitemap scan does (see GET .../sitemap/stats, a dashboard-only action today, not one this API exposes). Check scan_age_days to tell the two causes apart."}},"type":"object","required":["domain_id","total_sitemap_urls","blocked_urls_count","current_sitemap_url_count","stale"],"title":"SitemapConflictsSummaryPublic"},"SitemapDiscoveredUrlPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"url":{"type":"string","title":"Url"},"last_scanned_at":{"type":"string","format":"date-time","title":"Last Scanned At"},"status":{"type":"string","title":"Status","description":"'success' or 'failed'."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","url","last_scanned_at","status","created_at","updated_at"],"title":"SitemapDiscoveredUrlPublic"},"SitemapEntry":{"properties":{"sitemap_url":{"type":"string","title":"Sitemap Url"},"submitted_urls":{"type":"integer","title":"Submitted Urls"},"indexed_urls":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Indexed Urls","description":"How many of this sitemap's URLs Search Console reports as indexed, or null when it reported no indexed count for any sitemap on the property. See SitemapStatsPublic.indexed_reporting for the machine-readable reason why."},"errors":{"type":"integer","title":"Errors"},"warnings":{"type":"integer","title":"Warnings"},"last_checked":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked","description":"When Search Console last reported stats for this sitemap."}},"type":"object","required":["sitemap_url","submitted_urls","errors","warnings"],"title":"SitemapEntry"},"SitemapRateLimitPublic":{"properties":{"can_scan":{"type":"boolean","title":"Can Scan"},"reason":{"type":"string","title":"Reason"},"next_available_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Available At"},"last_scan_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Scan At"},"hours_until_next_scan":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hours Until Next Scan"}},"type":"object","required":["can_scan","reason"],"title":"SitemapRateLimitPublic"},"SitemapScanConfigPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"enabled":{"type":"boolean","title":"Enabled"},"auto_schedule_crux":{"type":"boolean","title":"Auto Schedule Crux"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","domain_id","enabled","auto_schedule_crux","created_at","updated_at"],"title":"SitemapScanConfigPublic"},"SlowCheckPublic":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"response_time_ms":{"type":"integer","title":"Response Time Ms"},"status_code":{"type":"integer","title":"Status Code"}},"type":"object","required":["timestamp","response_time_ms","status_code"],"title":"SlowCheckPublic"},"SlowChecksPagePublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"threshold_ms":{"type":"number","title":"Threshold Ms"},"items":{"items":{"$ref":"#/components/schemas/SlowCheckPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"type":"integer","title":"Total","description":"Slow checks in the window, capped at 100."}},"type":"object","required":["domain_id","start_date","end_date","threshold_ms","items","total"],"title":"SlowChecksPagePublic"},"SourceTotalsPublic":{"properties":{"direct":{"type":"integer","title":"Direct","default":0},"search":{"type":"integer","title":"Search","default":0},"internal":{"type":"integer","title":"Internal","default":0},"social":{"type":"integer","title":"Social","default":0},"referral":{"type":"integer","title":"Referral","default":0},"email":{"type":"integer","title":"Email","default":0},"newsletter":{"type":"integer","title":"Newsletter","default":0},"paid":{"type":"integer","title":"Paid","default":0},"paid_search":{"type":"integer","title":"Paid Search","default":0},"paid_social":{"type":"integer","title":"Paid Social","default":0},"ai_search":{"type":"integer","title":"Ai Search","default":0},"unknown":{"type":"integer","title":"Unknown","default":0},"other":{"type":"integer","title":"Other","default":0}},"type":"object","title":"SourceTotalsPublic","description":"Visitor counts by broad source category. Categories are fixed; unrecognized sources fall into ``other``."},"SourcesPublic":{"properties":{"by_source":{"items":{"$ref":"#/components/schemas/TrafficSourceMetrics"},"type":"array","title":"By Source"},"by_medium":{"items":{"$ref":"#/components/schemas/MediumMetrics"},"type":"array","title":"By Medium"},"by_campaign":{"items":{"$ref":"#/components/schemas/TrafficSourceMetrics"},"type":"array","title":"By Campaign"},"by_channel":{"items":{"$ref":"#/components/schemas/ChannelMetrics"},"type":"array","title":"By Channel"},"referrers":{"items":{"$ref":"#/components/schemas/ReferrerMetrics"},"type":"array","title":"Referrers"},"totals":{"$ref":"#/components/schemas/SourceTotalsPublic"},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that recorded a session (YYYY-MM-DD), or null when the window holds none. The same value GET /analytics/overview reports for the identical window, so a caller chaining one into the other is not told about a truncated window once and left to assume the second answer is complete. Check it before quoting a ranking as the window's ranking."}},"type":"object","title":"SourcesPublic"},"StateChangeEntry":{"properties":{"url":{"type":"string","title":"Url"},"inspected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Inspected At"},"coverage_state":{"type":"string","title":"Coverage State"},"rich_verdict":{"type":"string","title":"Rich Verdict"},"robots_state":{"type":"string","title":"Robots State"},"google_canonical":{"type":"string","title":"Google Canonical"},"last_crawl_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Crawl Time"},"prev_inspected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Prev Inspected At"},"prev_coverage_state":{"type":"string","title":"Prev Coverage State"},"prev_rich_verdict":{"type":"string","title":"Prev Rich Verdict"},"prev_robots_state":{"type":"string","title":"Prev Robots State"},"prev_google_canonical":{"type":"string","title":"Prev Google Canonical"},"prev_last_crawl_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Prev Last Crawl Time"}},"type":"object","required":["url","coverage_state","rich_verdict","robots_state","google_canonical","prev_coverage_state","prev_rich_verdict","prev_robots_state","prev_google_canonical"],"title":"StateChangeEntry"},"StateCount":{"properties":{"state":{"type":"string","title":"State"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["state","count"],"title":"StateCount"},"StatsExtraPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"p50_response_ms":{"type":"number","title":"P50 Response Ms"},"p75_response_ms":{"type":"number","title":"P75 Response Ms"},"p95_response_ms":{"type":"number","title":"P95 Response Ms"},"p99_response_ms":{"type":"number","title":"P99 Response Ms"},"mttr_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mttr Seconds","description":"Mean time to recovery across resolved incidents. Null if none."},"total_downtime_seconds":{"type":"integer","title":"Total Downtime Seconds"},"incidents_count":{"type":"integer","title":"Incidents Count"},"slow_check_count":{"type":"integer","title":"Slow Check Count","description":"Successful checks slower than the 95th percentile threshold."},"slow_check_threshold_ms":{"type":"number","title":"Slow Check Threshold Ms"}},"type":"object","required":["domain_id","start_date","end_date","p50_response_ms","p75_response_ms","p95_response_ms","p99_response_ms","total_downtime_seconds","incidents_count","slow_check_count","slow_check_threshold_ms"],"title":"StatsExtraPublic","description":"Percentiles, MTTR, and slow-check counts for the window."},"SubPillarScorePublic":{"properties":{"score":{"type":"number","title":"Score","default":0},"max":{"type":"number","title":"Max","default":0}},"type":"object","title":"SubPillarScorePublic"},"SuccessResponsePublic":{"properties":{"success":{"type":"boolean","title":"Success","default":true}},"type":"object","title":"SuccessResponsePublic"},"SyntheticAggregatedOpportunity":{"properties":{"opportunity_id":{"type":"string","title":"Opportunity Id","description":"Lighthouse's own audit id, e.g. 'unused-javascript', 'unminified-css'."},"title":{"type":"string","title":"Title","description":"Lighthouse's own title for the audit, server-authored."},"runs_seen":{"type":"integer","title":"Runs Seen","description":"How many of the runs actually read (runs_read on the envelope) raised this opportunity with a non-zero saving. This is the number that separates a consistent cost from one run's snapshot: an entry seen in 1 of 10 runs is a blip, one seen in 10 of 10 is a standing problem."},"typical_savings_ms":{"type":"integer","title":"Typical Savings Ms","description":"Median estimated saving in milliseconds across the runs that raised it. The median, not the mean, so one pathological run does not set the figure. Lighthouse's estimates are per-run and per-audit and are NOT additive - do not sum them across entries and quote a total."},"typical_savings_bytes":{"type":"integer","title":"Typical Savings Bytes","description":"Median estimated saving in bytes across the runs that raised it."},"max_savings_ms":{"type":"integer","title":"Max Savings Ms","description":"The largest millisecond saving any single run in the window estimated."},"max_savings_bytes":{"type":"integer","title":"Max Savings Bytes","description":"The largest byte saving any single run in the window estimated."}},"type":"object","required":["opportunity_id","title","runs_seen","typical_savings_ms","typical_savings_bytes","max_savings_ms","max_savings_bytes"],"title":"SyntheticAggregatedOpportunity"},"SyntheticAudit":{"properties":{"audit_id":{"type":"string","title":"Audit Id"},"audit_title":{"type":"string","title":"Audit Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score"},"passed":{"type":"boolean","title":"Passed"},"items":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Items"},"headings":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Headings"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Which Lighthouse category this audit is scored under: 'performance', 'accessibility', 'best-practices', 'seo' or 'pwa'. Without it a low best_practices_score could not be attributed to the audits that caused it - the audit list is one flat set covering every category. Read at request time from the stored raw document's categories[].auditRefs, so it is null for a test past the raw-data TTL and whenever audit_categories_notice explains why; an audit that more than one category references reports the first one."}},"type":"object","required":["audit_id","audit_title","passed"],"title":"SyntheticAudit"},"SyntheticAvailableLocation":{"properties":{"type":{"type":"string","title":"Type","description":"'google_psi' or 'pod'"},"pod_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"},"name":{"type":"string","title":"Name"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Pod health status, e.g. 'healthy', 'degraded'."}},"type":"object","required":["type","name"],"title":"SyntheticAvailableLocation"},"SyntheticBulkDeleteError":{"properties":{"url_id":{"type":"string","title":"Url Id"},"error":{"type":"string","title":"Error"}},"type":"object","required":["url_id","error"],"title":"SyntheticBulkDeleteError"},"SyntheticBulkDeleteRequest":{"properties":{"url_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Url Ids","description":"Max 200 per request."}},"type":"object","required":["url_ids"],"title":"SyntheticBulkDeleteRequest"},"SyntheticBulkDeleteResponse":{"properties":{"deleted":{"type":"integer","title":"Deleted"},"errors":{"items":{"$ref":"#/components/schemas/SyntheticBulkDeleteError"},"type":"array","title":"Errors"}},"type":"object","required":["deleted","errors"],"title":"SyntheticBulkDeleteResponse"},"SyntheticBulkFrequencyError":{"properties":{"url_id":{"type":"string","title":"Url Id"},"error":{"type":"string","title":"Error"}},"type":"object","required":["url_id","error"],"title":"SyntheticBulkFrequencyError"},"SyntheticBulkFrequencyRequest":{"properties":{"url_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Url Ids","description":"Max 200 per request."},"test_frequency":{"type":"string","title":"Test Frequency"}},"type":"object","required":["url_ids","test_frequency"],"title":"SyntheticBulkFrequencyRequest"},"SyntheticBulkFrequencyResponse":{"properties":{"updated":{"type":"integer","title":"Updated"},"errors":{"items":{"$ref":"#/components/schemas/SyntheticBulkFrequencyError"},"type":"array","title":"Errors"}},"type":"object","required":["updated","errors"],"title":"SyntheticBulkFrequencyResponse"},"SyntheticCompareResult":{"properties":{"period1_start":{"type":"string","format":"date-time","title":"Period1 Start","description":"The exact window this figure was computed over, resolved server-side. Always echoed, including when the caller named the window with period1/period2 rather than dates, and including when the caller named it neither way and it fell back to its default (period1 defaults to 'last_week', period2 to the complete calendar week immediately before it) - a preset or default with nothing echoed back left the caller unable to say which calendar days 'last_week' turned out to be, and unable to quote them."},"period1_end":{"type":"string","format":"date-time","title":"Period1 End","description":"The exact window this figure was computed over, resolved server-side. Always echoed, including when the caller named the window with period1/period2 rather than dates, and including when the caller named it neither way and it fell back to its default (period1 defaults to 'last_week', period2 to the complete calendar week immediately before it) - a preset or default with nothing echoed back left the caller unable to say which calendar days 'last_week' turned out to be, and unable to quote them."},"period2_start":{"type":"string","format":"date-time","title":"Period2 Start","description":"The exact window this figure was computed over, resolved server-side. Always echoed, including when the caller named the window with period1/period2 rather than dates, and including when the caller named it neither way and it fell back to its default (period1 defaults to 'last_week', period2 to the complete calendar week immediately before it) - a preset or default with nothing echoed back left the caller unable to say which calendar days 'last_week' turned out to be, and unable to quote them."},"period2_end":{"type":"string","format":"date-time","title":"Period2 End","description":"The exact window this figure was computed over, resolved server-side. Always echoed, including when the caller named the window with period1/period2 rather than dates, and including when the caller named it neither way and it fell back to its default (period1 defaults to 'last_week', period2 to the complete calendar week immediately before it) - a preset or default with nothing echoed back left the caller unable to say which calendar days 'last_week' turned out to be, and unable to quote them."},"period1_avg_performance_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period1 Avg Performance Score","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100. Null when period1_test_count is 0 - no Lighthouse run landed in this window, so there is no average to report. Not the same thing as a measured score of exactly 0.0, which is what this field used to return for an empty window (ClickHouse's avg() over zero rows is NaN, silently coerced to 0.0)."},"period2_avg_performance_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Period2 Avg Performance Score","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100. Null when period2_test_count is 0, for the same reason as period1_avg_performance_score."},"period1_test_count":{"type":"integer","title":"Period1 Test Count","description":"Lighthouse runs, for the requested strategy and location filter, inside period1. 0 means the window is empty: period1_avg_performance_score comes back null instead of a fabricated 0.0, and change_percentage/trend come back null too rather than compare against it."},"period2_test_count":{"type":"integer","title":"Period2 Test Count","description":"Same as period1_test_count, for period2."},"change_percentage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Change Percentage","description":"Percent change from the earlier window to the later one, whichever argument each was passed as. Positive means the score rose. Null when either window's test_count is 0 - a percent change against a window with no runs is not a measurement, and computing it against the fabricated 0.0 an empty window used to report read as a flat -100 per cent decline no matter what the other window scored."},"trend":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trend","description":"'improving', 'declining', or 'stable' (within 2 per cent), always read in chronological order regardless of which window was passed as period1. Null under the same condition as change_percentage - check period1_test_count/period2_test_count before reading a null trend as 'stable'."},"newest_test_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Newest Test Date","description":"The most recent test for this domain and location filter, at any time - the freshness anchor for the two windows compared above. Null when nothing has run in the last year."},"latest_test_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latest Test Age Days","description":"Days between newest_test_date and now."},"is_stale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Stale","description":"True when newest_test_date is older than twice the configured test_frequency. A stale location makes a period comparison unreliable in a specific way: if it stopped reporting between the two windows, the change figure is that location dropping out rather than the site's performance moving. Null when there is no data or the call spans URLs on different cadences."},"stale_locations_notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stale Locations Notice","description":"Only set when neither location_type nor pod_id was passed and the domain's locations differ materially in freshness, so both windows are blends of unequally-fresh sources. Pass location_type to compare one location cleanly."}},"type":"object","required":["period1_start","period1_end","period2_start","period2_end","period1_test_count","period2_test_count"],"title":"SyntheticCompareResult"},"SyntheticConsoleLog":{"properties":{"source":{"type":"string","title":"Source"},"level":{"type":"string","title":"Level","description":"'error', 'warning', 'info', 'log', 'debug', or 'verbose'."},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"timestamp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Timestamp","description":"Milliseconds from page load start."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"line_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Line Number"},"column_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Column Number"}},"type":"object","required":["source","level"],"title":"SyntheticConsoleLog"},"SyntheticCoreMetricsPublic":{"properties":{"lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Ms"},"cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls","description":"Cumulative Layout Shift score (unitless)."},"ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Ms"},"fcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcp Ms"},"si_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Si Ms","description":"Speed Index."},"tti_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tti Ms","description":"Time to Interactive."},"tbt_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tbt Ms","description":"Total Blocking Time."},"requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requests"},"transfer_size_bytes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Transfer Size Bytes"},"scores":{"anyOf":[{"$ref":"#/components/schemas/SyntheticScoresPublic"},{"type":"null"}]}},"type":"object","title":"SyntheticCoreMetricsPublic"},"SyntheticDeviceMetric":{"properties":{"value":{"type":"number","title":"Value"},"trend":{"anyOf":[{"$ref":"#/components/schemas/SyntheticMetricTrendValue"},{"type":"null"}]}},"type":"object","required":["value"],"title":"SyntheticDeviceMetric"},"SyntheticDeviceMetricsPublic":{"properties":{"lcp_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lcp Ms"},"cls":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cls","description":"Cumulative Layout Shift score (unitless)."},"ttfb_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ttfb Ms","description":"Server response time."},"requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Requests","description":"Network requests on the page."}},"type":"object","title":"SyntheticDeviceMetricsPublic","description":"Lab metrics for the domain's homepage, one device strategy.\n\nThe 75th percentile of the last five Lighthouse runs across all configured locations,\nso this is a rolling sample rather than a fixed window."},"SyntheticDeviceStats":{"properties":{"performance":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"accessibility":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"best_practices":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"seo":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"ttfb":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Server response time in ms; lower is better."},"lcp":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Largest Contentful Paint in ms; lower is better."},"cls":{"$ref":"#/components/schemas/SyntheticDeviceMetric","description":"Cumulative Layout Shift, unitless; lower is better."}},"type":"object","required":["performance","accessibility","best_practices","seo","ttfb","lcp","cls"],"title":"SyntheticDeviceStats"},"SyntheticDomainLocationWithData":{"properties":{"type":{"type":"string","title":"Type"},"pod_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"},"name":{"type":"string","title":"Name"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"test_count":{"type":"integer","title":"Test Count","default":0}},"type":"object","required":["type","name"],"title":"SyntheticDomainLocationWithData"},"SyntheticFilmstripFrame":{"properties":{"timestamp":{"type":"integer","title":"Timestamp","description":"Milliseconds from page load start."},"data":{"type":"string","title":"Data","description":"Base64 image data, or an R2/S3 URL when offloaded."}},"type":"object","required":["timestamp","data"],"title":"SyntheticFilmstripFrame"},"SyntheticFrequencyUpdateRequest":{"properties":{"test_frequency":{"type":"string","enum":["1h","4h","8h","12h","24h"],"title":"Test Frequency"}},"type":"object","required":["test_frequency"],"title":"SyntheticFrequencyUpdateRequest"},"SyntheticLocationCreate":{"properties":{"location_type":{"type":"string","enum":["google_psi","pod"],"title":"Location Type"},"pod_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id","description":"Required when location_type is 'pod'."}},"type":"object","required":["location_type"],"title":"SyntheticLocationCreate"},"SyntheticLocationLimits":{"properties":{"max_locations":{"type":"integer","title":"Max Locations"},"package_name":{"type":"string","title":"Package Name"},"allow_pod_locations":{"type":"boolean","title":"Allow Pod Locations","description":"False on the free plan, which allows only Google's Servers."}},"type":"object","required":["max_locations","package_name","allow_pod_locations"],"title":"SyntheticLocationLimits"},"SyntheticLocationPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"location_type":{"type":"string","title":"Location Type"},"pod_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"},"pod_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pod Name"},"location_name":{"type":"string","title":"Location Name"},"location_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Code"},"is_active":{"type":"boolean","title":"Is Active","default":true}},"type":"object","required":["id","location_type","location_name"],"title":"SyntheticLocationPublic"},"SyntheticMetricDataPoint":{"properties":{"tested_at":{"type":"string","format":"date-time","title":"Tested At"},"metric_value":{"type":"number","title":"Metric Value"},"metric_score":{"type":"number","title":"Metric Score"}},"type":"object","required":["tested_at","metric_value","metric_score"],"title":"SyntheticMetricDataPoint"},"SyntheticMetricTrend":{"properties":{"metric_name":{"type":"string","title":"Metric Name"},"trend":{"items":{"$ref":"#/components/schemas/SyntheticMetricDataPoint"},"type":"array","title":"Trend"}},"type":"object","required":["metric_name","trend"],"title":"SyntheticMetricTrend"},"SyntheticMetricTrendValue":{"properties":{"percentage":{"type":"number","title":"Percentage"},"direction":{"type":"string","title":"Direction","description":"'better', 'worse', or 'same'"}},"type":"object","required":["percentage","direction"],"title":"SyntheticMetricTrendValue"},"SyntheticMetricsComparisonPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/SyntheticCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/SyntheticCoreMetricsPublic"},{"type":"null"}]}},"type":"object","title":"SyntheticMetricsComparisonPublic"},"SyntheticMetricsPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/SyntheticCoreMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/SyntheticCoreMetricsPublic"},{"type":"null"}]},"comparison":{"anyOf":[{"$ref":"#/components/schemas/SyntheticMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"SyntheticMetricsPublic"},"SyntheticNetworkRequest":{"properties":{"url":{"type":"string","title":"Url"},"resource_type":{"type":"string","title":"Resource Type"},"start_time":{"type":"number","title":"Start Time","description":"Milliseconds from page load start."},"end_time":{"type":"number","title":"End Time","description":"Milliseconds from page load start."},"transfer_size":{"type":"integer","title":"Transfer Size","description":"Bytes transferred over the network."},"status_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status Code"},"resource_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Resource Size","description":"Uncompressed size in bytes."},"protocol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Protocol"},"mime_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime Type"},"priority":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Priority"},"entity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity"},"finished":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Finished"},"from_main_frame":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"From Main Frame"}},"type":"object","required":["url","resource_type","start_time","end_time","transfer_size"],"title":"SyntheticNetworkRequest"},"SyntheticOpportunitiesResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticAggregatedOpportunity"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"strategy":{"type":"string","title":"Strategy","description":"The strategy these runs were drawn from. Mobile and desktop runs of the same page surface materially different opportunities, so one call never blends them."},"runs_requested":{"type":"integer","title":"Runs Requested","description":"The 'runs' parameter, after clamping to 1-20."},"runs_found":{"type":"integer","title":"Runs Found","description":"Runs matching the domain, strategy and any url/location filter that existed to read. Lower than runs_requested means the domain has not run that many tests under this filter, not that data was dropped."},"runs_read":{"type":"integer","title":"Runs Read","description":"Runs that still had stored audit rows and therefore contributed to the figures below. This is the denominator for every runs_seen. Zero means the aggregate is empty because nothing could be read, NOT because the site has no opportunities."},"runs_without_data":{"type":"integer","title":"Runs Without Data","description":"runs_found minus runs_read: runs that exist in the test history but whose audit rows are gone (aged out past their TTL, or never stored). They contribute nothing, and an opportunity they would have raised is missing from this list rather than resolved. Treat a non-zero value here as the answer being partial."},"window_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Window Start","description":"tested_at of the oldest run considered. Null when runs_found is 0."},"window_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Window End","description":"tested_at of the newest run considered. Null when runs_found is 0."},"coverage_notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Coverage Notice","description":"Plain-language statement of any gap in what was read - set whenever runs_without_data is non-zero or nothing could be read at all. Null when every run found was read in full. Quote it alongside any recommendation."}},"type":"object","required":["items","strategy","runs_requested","runs_found","runs_read","runs_without_data"],"title":"SyntheticOpportunitiesResponse","description":"Opportunities across the last N runs, with what the aggregate actually covered.\n\nEvery count here exists because the alternative is a silent lie: a run whose stored\naudit rows have aged out and a run that genuinely had nothing to fix both contribute\nzero rows, and reporting the first as the second turns missing data into a solved\nopportunity."},"SyntheticOpportunity":{"properties":{"opportunity_id":{"type":"string","title":"Opportunity Id"},"title":{"type":"string","title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"savings_ms":{"type":"integer","title":"Savings Ms"},"savings_bytes":{"type":"integer","title":"Savings Bytes"},"items":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Items"},"headings":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Headings"}},"type":"object","required":["opportunity_id","title","savings_ms","savings_bytes"],"title":"SyntheticOpportunity"},"SyntheticOverviewPublic":{"properties":{"mobile":{"anyOf":[{"$ref":"#/components/schemas/SyntheticDeviceMetricsPublic"},{"type":"null"}]},"desktop":{"anyOf":[{"$ref":"#/components/schemas/SyntheticDeviceMetricsPublic"},{"type":"null"}]}},"type":"object","title":"SyntheticOverviewPublic"},"SyntheticOverviewResponse":{"properties":{"total_tests":{"type":"integer","title":"Total Tests","description":"Tests in the current 28-day window (mobile + desktop)."},"latest_test_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Test Date","description":"Newest test INSIDE the 28-complete-day averaging window every score and metric here is computed over. That window ends at the start of today, so this is at most yesterday's test even on a domain tested hourly - it dates the averages, and is not a freshness signal. Read newest_test_date for 'when did this domain last run', which is what is_stale is judged against."},"newest_test_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Newest Test Date","description":"The most recent test overall for this domain and any location/url filter, NOT bounded by the averaging window - the same value list_synthetic_tests reports. This is the freshness signal, and the value latest_test_age_days and is_stale are computed from. Null when nothing has run in the last year."},"mobile":{"$ref":"#/components/schemas/SyntheticDeviceStats"},"desktop":{"$ref":"#/components/schemas/SyntheticDeviceStats"},"mobile_tests_count":{"type":"integer","title":"Mobile Tests Count"},"desktop_tests_count":{"type":"integer","title":"Desktop Tests Count"},"latest_test_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latest Test Age Days","description":"Days between newest_test_date and now, computed server-side so a caller does not have to do date arithmetic. Null when no test has ever run. Note this is the age of newest_test_date, not of latest_test_date."},"is_stale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Stale","description":"True when newest_test_date is older than twice the configured test_frequency for the location(s) this overview is drawn from - judged against that cadence, not a fixed constant, and against the newest test overall rather than the newest one inside the averaging window (which would read as stale every day for any cadence of 12h or shorter). Null when there is no data, or when the call spans multiple URLs on different test_frequency settings so no single cadence applies; see stale_locations_notice for that case. Pass url_id and/or location_type to pin this to one cadence."},"stale_locations_notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stale Locations Notice","description":"Only set when this call was NOT scoped to one location_type/pod_id and the average still blends locations of materially different freshness - names the location that is behind and by how many days. This is the case a caller who never asks about locations at all cannot otherwise know to check for: compare two location_type calls' latest_test_age_days/is_stale before reading a score difference between them as a quality gap rather than two different time windows."}},"type":"object","required":["total_tests","mobile","desktop","mobile_tests_count","desktop_tests_count"],"title":"SyntheticOverviewResponse"},"SyntheticScoresPublic":{"properties":{"performance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Performance"},"accessibility":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Accessibility"},"best_practices":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Best Practices"},"seo":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seo"},"pwa":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pwa"}},"type":"object","title":"SyntheticScoresPublic","description":"Lighthouse category scores, 0-100."},"SyntheticTestDetail":{"properties":{"test_id":{"type":"string","format":"uuid","title":"Test Id"},"tested_at":{"type":"string","format":"date-time","title":"Tested At"},"strategy":{"type":"string","title":"Strategy"},"performance_score":{"type":"number","title":"Performance Score"},"accessibility_score":{"type":"number","title":"Accessibility Score"},"best_practices_score":{"type":"number","title":"Best Practices Score"},"seo_score":{"type":"number","title":"Seo Score"},"pwa_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pwa Score"},"metrics":{"items":{"$ref":"#/components/schemas/SyntheticMetricTrend"},"type":"array","title":"Metrics"},"audits":{"items":{"$ref":"#/components/schemas/SyntheticAudit"},"type":"array","title":"Audits"},"opportunities":{"items":{"$ref":"#/components/schemas/SyntheticOpportunity"},"type":"array","title":"Opportunities"},"screenshot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot","description":"Base64 final screenshot (legacy storage)."},"screenshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Screenshot Url","description":"R2/S3 URL for the final screenshot."},"filmstrip_screenshots":{"items":{"$ref":"#/components/schemas/SyntheticFilmstripFrame"},"type":"array","title":"Filmstrip Screenshots"},"filmstrip_urls":{"items":{"type":"string"},"type":"array","title":"Filmstrip Urls"},"network_requests":{"items":{"$ref":"#/components/schemas/SyntheticNetworkRequest"},"type":"array","title":"Network Requests"},"console_logs":{"items":{"$ref":"#/components/schemas/SyntheticConsoleLog"},"type":"array","title":"Console Logs"},"metadata":{"anyOf":[{"$ref":"#/components/schemas/SyntheticTestMetadata"},{"type":"null"}]},"dcl_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dcl Ms","description":"DOMContentLoaded time, ms from navigation start."},"load_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Load Ms","description":"Load event time, ms from navigation start."},"raw_data_available":{"type":"boolean","title":"Raw Data Available","description":"False once the 12-month raw-data TTL has expired for this test.","default":true},"screenshots_available":{"type":"boolean","title":"Screenshots Available","description":"False once the 90-day screenshot TTL has expired for this test.","default":true},"audit_categories_notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Audit Categories Notice","description":"Set only when every audit's 'category' had to come back null, and says why - most often that the raw Lighthouse document behind this test has aged out, so the categories[].auditRefs mapping the category is read from no longer exists. Null when the mapping was available, whether or not every audit was found in it. A described reason rather than a guessed category."}},"type":"object","required":["test_id","tested_at","strategy","performance_score","accessibility_score","best_practices_score","seo_score","metrics","audits","opportunities"],"title":"SyntheticTestDetail"},"SyntheticTestMetadata":{"properties":{"fetch_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetch Time"},"lighthouse_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lighthouse Version"},"user_agent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Agent"},"emulated_form_factor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emulated Form Factor"},"throttling_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Throttling Method"},"gather_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gather Mode"}},"type":"object","title":"SyntheticTestMetadata"},"SyntheticTimelineEntry":{"properties":{"tested_at":{"type":"string","format":"date-time","title":"Tested At"},"test_id":{"type":"string","format":"uuid","title":"Test Id"},"strategy":{"type":"string","title":"Strategy"},"performance_score":{"type":"number","title":"Performance Score","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"accessibility_score":{"type":"number","title":"Accessibility Score"},"best_practices_score":{"type":"number","title":"Best Practices Score"},"seo_score":{"type":"number","title":"Seo Score"},"pwa_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pwa Score"},"location_type":{"type":"string","title":"Location Type","default":"google_psi"},"pod_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Pod Id"},"location_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Name"},"location_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location Code"}},"type":"object","required":["tested_at","test_id","strategy","performance_score","accessibility_score","best_practices_score","seo_score"],"title":"SyntheticTimelineEntry"},"SyntheticTimelineResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticTimelineEntry"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"latest_test_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latest Test Age Days","description":"Days between the newest entry's tested_at and today. Null when items is empty."},"is_stale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Stale","description":"True when latest_test_age_days exceeds twice the resolved test_frequency. Null when items is empty, or no single cadence applies to this call."}},"type":"object","required":["items"],"title":"SyntheticTimelineResponse","description":"The run-history list, with a staleness signal for the newest entry.\n\nlatest_test_age_days/is_stale are only populated when this call resolves to one\ncadence: url_id alone is always enough (test_frequency is set per URL, shared by\nevery location on it), and without url_id a location_type/pod_id filter still works\nif every matching URL happens to share one frequency. Both are null when no single\ncadence applies - most often an unfiltered, multi-URL, multi-location call."},"SyntheticUpdateLocationsRequest":{"properties":{"locations":{"items":{"$ref":"#/components/schemas/SyntheticLocationCreate"},"type":"array","minItems":1,"title":"Locations"}},"type":"object","required":["locations"],"title":"SyntheticUpdateLocationsRequest"},"SyntheticUrlBulkCreateRequest":{"properties":{"urls":{"items":{"type":"string"},"type":"array","maxItems":1000,"title":"Urls","description":"Full URLs to monitor."},"test_frequency":{"type":"string","enum":["1h","4h","8h","12h","24h"],"title":"Test Frequency","default":"24h"},"locations":{"items":{"$ref":"#/components/schemas/SyntheticLocationCreate"},"type":"array","title":"Locations","description":"Locations applied to every URL in this batch. Defaults to Google's Servers."}},"type":"object","required":["urls"],"title":"SyntheticUrlBulkCreateRequest"},"SyntheticUrlBulkCreateResponse":{"properties":{"added":{"type":"integer","title":"Added"},"duplicates":{"items":{"type":"string"},"type":"array","title":"Duplicates"},"failed":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Failed","description":"Entries shaped {url, reason} for URLs that could not be added."}},"type":"object","required":["added"],"title":"SyntheticUrlBulkCreateResponse"},"SyntheticUrlListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticUrlPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"page":{"type":"integer","title":"Page","default":1},"page_size":{"type":"integer","title":"Page Size","default":50},"total_pages":{"type":"integer","title":"Total Pages","default":1}},"type":"object","required":["items"],"title":"SyntheticUrlListResponse"},"SyntheticUrlLocationsPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SyntheticLocationPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"url_id":{"type":"string","format":"uuid","title":"Url Id"}},"type":"object","required":["items","url_id"],"title":"SyntheticUrlLocationsPage","description":"The full location set for one URL, for the GET read."},"SyntheticUrlLocationsResponse":{"properties":{"url_id":{"type":"string","format":"uuid","title":"Url Id"},"locations":{"items":{"$ref":"#/components/schemas/SyntheticLocationPublic"},"type":"array","title":"Locations"}},"type":"object","required":["url_id","locations"],"title":"SyntheticUrlLocationsResponse","description":"The full location set for one URL, returned as-is by the replace (PUT) operation.\n\nDeliberately NOT the paginated envelope (see ``SyntheticUrlLocationsPage`` for the\nGET read) - a PUT that replaces the set is not a list read, it is an operation result\nthat happens to include the new set (``docs/modules/public-api.md`` list-envelope plan,\n\"deliberately not changed\")."},"SyntheticUrlPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"url":{"type":"string","title":"Url"},"is_active":{"type":"boolean","title":"Is Active"},"test_frequency":{"type":"string","title":"Test Frequency"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"latest_test_date":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Latest Test Date"},"latest_mobile_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latest Mobile Score","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"latest_desktop_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latest Desktop Score","description":"Lighthouse score as a 0-1 fraction, the scale Lighthouse itself uses. Multiply by 100 for the familiar score out of 100: 0.51 is 51/100."},"test_count":{"type":"integer","title":"Test Count","description":"Distinct test runs (a mobile+desktop pair counts as one).","default":0},"location_count":{"type":"integer","title":"Location Count","default":1},"latest_test_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latest Test Age Days","description":"Days between latest_test_date and today, computed server-side so a caller does not have to do date arithmetic. Null when no test has ever run."},"is_stale":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Stale","description":"True when latest_test_age_days exceeds twice this URL's own test_frequency - the signal that catches a location which has silently stopped reporting while is_active stays true. Judged against test_frequency on this same row, not a fixed constant: a 7d-cadence URL last tested 5 days ago is not stale, while a 12h-cadence URL last tested 19 days ago is. Null when no test has ever run."}},"type":"object","required":["id","domain_id","url","is_active","test_frequency","created_at","updated_at"],"title":"SyntheticUrlPublic"},"TTFBSubpartsTimelineItemPublic":{"properties":{"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"avg_dns_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Dns Time Ms"},"avg_connection_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Connection Time Ms"},"avg_tls_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Tls Time Ms"},"avg_request_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Request Time Ms"},"avg_response_time_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Response Time Ms"}},"type":"object","title":"TTFBSubpartsTimelineItemPublic"},"TTFBSubpartsTimelinePageResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TTFBSubpartsTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."}},"type":"object","required":["items","window_start","window_end"],"title":"TTFBSubpartsTimelinePageResponse"},"TestURLBatchRequest":{"properties":{"urls":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Urls"},"user_agents":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":25},{"type":"null"}],"title":"User Agents"},"content":{"anyOf":[{"type":"string","maxLength":1048576},{"type":"null"}],"title":"Content","description":"Candidate robots.txt content to test against, instead of the stored one."}},"type":"object","required":["urls"],"title":"TestURLBatchRequest"},"TestURLResultPublic":{"properties":{"url":{"type":"string","title":"Url"},"user_agent":{"type":"string","title":"User Agent"},"allowed":{"type":"boolean","title":"Allowed"},"matched_rule":{"type":"string","title":"Matched Rule"}},"type":"object","required":["url","user_agent","allowed","matched_rule"],"title":"TestURLResultPublic"},"TimelinePublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/app__schemas__public__analytics__TimelinePoint"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"granularity":{"type":"string","enum":["hour","day","week"],"title":"Granularity","default":"day"},"totals":{"$ref":"#/components/schemas/TimelineTotalsPublic"}},"type":"object","required":["items"],"title":"TimelinePublic"},"TimelineTotalsPublic":{"properties":{"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0}},"type":"object","title":"TimelineTotalsPublic"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT bearer token. Send as 'Authorization: Bearer <token>'. Bound to the 'resource' chosen at authorization time and rejected by any other API."},"token_type":{"type":"string","title":"Token Type","default":"Bearer"},"expires_in":{"type":"integer","title":"Expires In","description":"Access token lifetime in seconds."},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token","description":"Single-use. Every refresh returns a new one and invalidates this one; presenting a superseded refresh token revokes the entire grant, because the only way to hold one is to have obtained it after we rotated it away."},"scope":{"type":"string","title":"Scope","description":"Space-delimited scopes actually granted."}},"type":"object","required":["access_token","expires_in","scope"],"title":"TokenResponse","description":"RFC 6749 \u00a75.1 successful token response."},"TopBlockedPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TopBlockedURLPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"window_days":{"type":"integer","title":"Window Days"}},"type":"object","required":["items","domain_id","window_days"],"title":"TopBlockedPublic"},"TopBlockedURLPublic":{"properties":{"url":{"type":"string","title":"Url"},"source":{"type":"string","title":"Source","description":"'gsc' (search clicks), 'wa' (pageviews), or 'both'."},"clicks":{"type":"integer","title":"Clicks","default":0},"impressions":{"type":"integer","title":"Impressions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"matched_rule":{"type":"string","title":"Matched Rule","default":""}},"type":"object","required":["url","source"],"title":"TopBlockedURLPublic"},"TrafficSourceMetrics":{"properties":{"source":{"type":"string","title":"Source"},"medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Medium"},"campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign"},"visitors":{"type":"integer","title":"Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"type":"number","title":"Bounce Rate","default":0.0},"avg_session_duration_seconds":{"type":"number","title":"Avg Session Duration Seconds","default":0.0}},"type":"object","required":["source"],"title":"TrafficSourceMetrics","description":"One traffic source's visitor, session, and engagement metrics.\n\nReturned fully populated by ``GET /analytics/sources``. The overview's abbreviated rows\nare ``OverviewSourceSummary``."},"TrendBreachPublic":{"properties":{"t":{"type":"string","title":"T","description":"Timestamp of the reading that triggered the alert."},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value","description":"Null when the series cannot vouch for a y-value, e.g. a pct-change or anomaly alert whose metric_value is itself a percentage, not a raw point."}},"type":"object","required":["t"],"title":"TrendBreachPublic"},"TrendPointPublic":{"properties":{"t":{"type":"string","title":"T","description":"ISO-8601 timestamp of this reading."},"value":{"type":"number","title":"Value"}},"type":"object","required":["t","value"],"title":"TrendPointPublic"},"TruncatedUserAgentGroupPublic":{"properties":{"user_agent_group":{"type":"string","title":"User Agent Group","description":"The skipped group, lower-cased as the analyser stores it."},"rule_count":{"type":"integer","title":"Rule Count","description":"How many rules the group had when it was skipped."}},"type":"object","required":["user_agent_group","rule_count"],"title":"TruncatedUserAgentGroupPublic","description":"A user-agent group the overlap analysis skipped whole because it was too large."},"URLGroupCreateRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name"},"path_prefix":{"type":"string","maxLength":500,"minLength":1,"title":"Path Prefix"},"gsc_property_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Gsc Property Id","description":"Must be one of this domain's enrolled indexing properties."},"sort_order":{"type":"integer","maximum":32767.0,"minimum":0.0,"title":"Sort Order","default":100},"color":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Color"}},"type":"object","required":["name","path_prefix"],"title":"URLGroupCreateRequest"},"URLGroupPublic":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"name":{"type":"string","title":"Name"},"path_prefix":{"type":"string","title":"Path Prefix"},"gsc_property_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Gsc Property Id"},"sort_order":{"type":"integer","title":"Sort Order"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"auto_suggested":{"type":"boolean","title":"Auto Suggested"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["id","domain_id","name","path_prefix","sort_order","auto_suggested"],"title":"URLGroupPublic"},"URLGroupUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name"},"path_prefix":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Path Prefix"},"gsc_property_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Gsc Property Id"},"sort_order":{"anyOf":[{"type":"integer","maximum":32767.0,"minimum":0.0},{"type":"null"}],"title":"Sort Order"},"color":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Color"}},"type":"object","title":"URLGroupUpdateRequest"},"UnreadCountPublic":{"properties":{"unread_count":{"type":"integer","title":"Unread Count"}},"type":"object","required":["unread_count"],"title":"UnreadCountPublic"},"UptimeAvailabilityPreviousPublic":{"properties":{"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"uptime_pct":{"type":"number","title":"Uptime Pct"},"total_checks":{"type":"integer","title":"Total Checks"},"successful_checks":{"type":"integer","title":"Successful Checks"},"failed_checks":{"type":"integer","title":"Failed Checks"},"avg_response_time_ms":{"type":"number","title":"Avg Response Time Ms"}},"type":"object","required":["start_date","end_date","uptime_pct","total_checks","successful_checks","failed_checks","avg_response_time_ms"],"title":"UptimeAvailabilityPreviousPublic","description":"The comparison window's own totals and exact dates. Present only when the request\nset compare_previous=true - see compare_mode on the endpoint for how the window is\nchosen."},"UptimeAvailabilityPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"uptime_pct":{"type":"number","title":"Uptime Pct"},"total_checks":{"type":"integer","title":"Total Checks"},"successful_checks":{"type":"integer","title":"Successful Checks"},"failed_checks":{"type":"integer","title":"Failed Checks"},"avg_response_time_ms":{"type":"number","title":"Avg Response Time Ms"},"previous":{"anyOf":[{"$ref":"#/components/schemas/UptimeAvailabilityPreviousPublic"},{"type":"null"}],"description":"The comparison window, present only when the request set compare_previous=true. compare_mode chooses how it is picked: 'previous_period' (default) is the immediately preceding window of equal length; 'calendar_month' is the full calendar month before the month this window ends in, regardless of this window's own length - use that for a 'compared to last month' question. GET /health's own uptime comparison is a separate, fixed trailing 28-day window and can disagree with either mode here; its response now names its own compare_range_start/end so which window produced which verdict is never ambiguous."},"uptime_pct_change_pp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Pct Change Pp","description":"uptime_pct minus previous.uptime_pct, in percentage POINTS, not a relative percent change - deliberately not named *_change_pct, to avoid reporting a percentage of a percentage. Positive means uptime improved. Populated only when compare_previous=true."}},"type":"object","required":["domain_id","start_date","end_date","uptime_pct","total_checks","successful_checks","failed_checks","avg_response_time_ms"],"title":"UptimeAvailabilityPublic"},"UptimeConfigPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"check_interval_seconds":{"type":"integer","title":"Check Interval Seconds"},"timeout_seconds":{"type":"integer","title":"Timeout Seconds"},"enabled":{"type":"boolean","title":"Enabled"},"alert_on_down":{"type":"boolean","title":"Alert On Down"}},"type":"object","required":["domain_id","check_interval_seconds","timeout_seconds","enabled","alert_on_down"],"title":"UptimeConfigPublic"},"UptimeMetricsComparisonPublic":{"properties":{"uptime_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Pct"},"median_response_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Response Ms"},"incidents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Incidents"}},"type":"object","title":"UptimeMetricsComparisonPublic"},"UptimeMetricsPublic":{"properties":{"uptime_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Pct"},"median_response_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Response Ms","description":"Median response time over successful HTTP checks in the window, not a mean and not over failed checks."},"incidents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Incidents","description":"Distinct downtime incidents in the window, NOT the number of failed checks. get_uptime_timeline's per-bucket 'failed_checks' counts individual check failures and is far larger; the two are not comparable."},"is_up":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Up","description":"Result of the most recent check. Only as fresh as 'meta'."},"ssl_days_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ssl Days Remaining"},"domain_days_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Domain Days Remaining","description":"Days until domain registration expiry."},"comparison":{"anyOf":[{"$ref":"#/components/schemas/UptimeMetricsComparisonPublic"},{"type":"null"}]}},"type":"object","title":"UptimeMetricsPublic"},"UptimeOverviewPublic":{"properties":{"uptime_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime Pct","description":"Availability over the last 28 days."},"ssl_days_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ssl Days Remaining","description":"Days until the TLS certificate expires. Floored at 0."},"domain_days_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Domain Days Remaining","description":"Days until domain registration expires. Negative once expired."},"is_blocked_403":{"type":"boolean","title":"Is Blocked 403","description":"The last five checks all returned 403, so monitoring is being blocked rather than the site being down. Treat uptime_pct as unreliable when this is true.","default":false}},"type":"object","title":"UptimeOverviewPublic"},"UptimeStatusPublic":{"properties":{"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"status_code":{"type":"integer","title":"Status Code"},"response_time_ms":{"type":"integer","title":"Response Time Ms"},"is_up":{"type":"boolean","title":"Is Up"},"error_message":{"type":"string","title":"Error Message","default":""}},"type":"object","required":["domain_id","timestamp","status_code","response_time_ms","is_up"],"title":"UptimeStatusPublic","description":"Result of the most recent check performed by the Go uptime monitor."},"UptimeTimelineCheckPublic":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"status_code":{"type":"integer","title":"Status Code"},"response_time_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Time Ms","description":"Mean over the successful checks in the bucket, null when none succeeded. Skip null buckets when averaging: they mean 'no response was recorded', not 'the response was instant'. A week containing one fully-down day averaged 190ms with those buckets counted as 0 and 222ms without."},"is_up":{"type":"boolean","title":"Is Up","description":"Majority verdict for the bucket, NOT 'no check failed'. At week or month granularity a bucket holds thousands of checks, so read uptime_pct for the real figure and failed_checks for what went wrong inside a mostly-up bucket."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"},"checks":{"type":"integer","title":"Checks","description":"Checks performed in this bucket.","default":0},"failed_checks":{"type":"integer","title":"Failed Checks","description":"Of those, how many failed.","default":0},"uptime_pct":{"type":"number","title":"Uptime Pct","description":"Percentage of checks that succeeded.","default":100.0},"expected_checks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Checks","description":"How many checks this bucket could have held: the part of the bucket that falls inside the requested window, divided by check_interval_seconds. Clamped to the window, so the first and last buckets are not reported as thin merely for being partial."},"coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage Pct","description":"checks / expected_checks as a percentage, capped at 100. Read this before reading a spike: a bucket built from 75 checks and one built from 1,426 both report a single uptime_pct, and only this field tells them apart."},"partial_coverage":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Partial Coverage","description":"True when coverage_pct is under 80 - the checker missed a material share of this bucket, so uptime_pct and response_time_ms here are computed from a thin sample. Not an outage in itself; a bucket absent from 'items' entirely is the stronger signal, counted in missing_buckets."}},"type":"object","required":["timestamp","status_code","is_up"],"title":"UptimeTimelineCheckPublic","description":"One granularity bucket, aggregated over every check inside it."},"UptimeTimelinePublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UptimeTimelineCheckPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"granularity":{"type":"string","title":"Granularity"},"total_checks":{"type":"integer","title":"Total Checks","description":"Checks in the full window, independent of the 5000-bucket cap on 'items'."},"uptime_pct":{"type":"number","title":"Uptime Pct"},"check_interval_seconds":{"type":"integer","title":"Check Interval Seconds","description":"How often this domain is configured to be checked - the divisor behind every bucket's expected_checks."},"expected_buckets":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Buckets","description":"Buckets this window would contain if no check had ever been missed. Null when has_more is true, because the 5000-bucket cap has already dropped older buckets and the difference would no longer mean 'missing'."},"missing_buckets":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Missing Buckets","description":"expected_buckets minus the buckets actually returned: intervals where NO check ran at all, which is a different fact from a bucket that ran few (partial_coverage). A gap of several consecutive days reads as an unbroken line on a chart unless this is checked. Null when has_more is true."}},"type":"object","required":["items","domain_id","start_date","end_date","granularity","total_checks","uptime_pct","check_interval_seconds"],"title":"UptimeTimelinePublic"},"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"},"VitalTimelineItemPublic":{"properties":{"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"metric_name":{"type":"string","title":"Metric Name"},"device_type":{"type":"string","title":"Device Type"},"avg_value":{"type":"number","title":"Avg Value"},"p50":{"type":"number","title":"P50"},"p75":{"type":"number","title":"P75"},"p90":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90"},"p95":{"type":"number","title":"P95"}},"type":"object","required":["timestamp","metric_name","device_type","avg_value","p50","p75","p95"],"title":"VitalTimelineItemPublic","description":"One time bucket for one metric/device combination.\n\nSee :class:`CoreWebVitalPublic` for the unit note \u2013 it applies here too."},"VitalTimelinePublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VitalTimelineItemPublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date","description":"Same value as window_start below - kept for backward compatibility."},"end_date":{"type":"string","format":"date-time","title":"End Date","description":"Same value as window_end below - kept for backward compatibility."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest date the returned items actually reach (YYYY-MM-DD), or null when none do. A window whose collection has stopped or lags simply stops producing points short of end_date, and an empty tail looks identical to a genuinely quiet one without this field - check it before reading the series as covering the whole requested window."}},"type":"object","required":["items","domain_id","start_date","end_date","window_start","window_end"],"title":"VitalTimelinePublicResponse"},"VitalsByDeviceTypePublic":{"properties":{"device_type":{"type":"string","title":"Device Type"},"metrics":{"items":{"$ref":"#/components/schemas/CoreWebVitalPublic"},"type":"array","title":"Metrics"}},"type":"object","required":["device_type","metrics"],"title":"VitalsByDeviceTypePublic"},"VitalsPublicResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VitalsByDeviceTypePublic"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"start_date":{"type":"string","format":"date-time","title":"Start Date","description":"Same value as window_start below - kept for backward compatibility."},"end_date":{"type":"string","format":"date-time","title":"End Date","description":"Same value as window_end below - kept for backward compatibility."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Effective UTC start of the window this request actually queried - may differ from any start_date you passed, since the window is widened/defaulted before querying. Compare this field, not the request parameters, when reconciling two calls by hand."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Effective UTC end of the window this request actually queried. An explicit end_date is honoured to the end of the day it names: the whole-day extension the internal handlers apply for the dashboard (a deliberate allowance for a browser in a positive UTC offset sending the previous day's timestamp) is cancelled on this surface, so end_date=2026-07-31 ends at 2026-07-31T23:59:59.999999 and does not count 2026-08-01 events. Responses published before this release did include that following day. When you pass no end_date the window is resolved at request time, so on a cached response this can be newer than the data: read data_through, not this field, for data freshness."},"data_through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Through","description":"Newest day inside the requested window that has data (YYYY-MM-DD), or null when the window holds none. Both this endpoint and GET .../rum/overview default to a trailing 30-day window ending now, so when collection has stopped or lags, that default silently shrinks to whatever days actually overlap real data - on one domain a 30-day default held only 12 real days, and this endpoint reported a 75th-percentile LCP 2.3x the value GET .../rum/overview reported for the same nominal window. Pass explicit start_date/end_date to both endpoints and compare data_through before trusting agreement between them."}},"type":"object","required":["items","domain_id","start_date","end_date","window_start","window_end"],"title":"VitalsPublicResponse"},"WidgetConfigPublic":{"properties":{"id":{"type":"string","title":"Id"},"enabled":{"type":"boolean","title":"Enabled","default":true},"order":{"type":"integer","title":"Order","default":0},"config":{"additionalProperties":true,"type":"object","title":"Config"}},"type":"object","required":["id"],"title":"WidgetConfigPublic","description":"One widget's enabled/order/config state within a section."},"WorkspaceSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan"},"domain_count":{"type":"integer","title":"Domain Count","default":0},"subscription_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Status"},"data_retention_months":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Data Retention Months"}},"type":"object","required":["id","name"],"title":"WorkspaceSummary"},"app__schemas__public__analytics__TimelinePoint":{"properties":{"date":{"type":"string","title":"Date","description":"Bucket start. 'YYYY-MM-DD' for day/week granularity, an ISO 8601 UTC timestamp for hour."},"visitors":{"type":"integer","title":"Visitors","default":0},"new_visitors":{"type":"integer","title":"New Visitors","default":0},"sessions":{"type":"integer","title":"Sessions","default":0},"pageviews":{"type":"integer","title":"Pageviews","default":0},"bounce_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bounce Rate"},"avg_duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Duration Seconds"}},"type":"object","required":["date"],"title":"TimelinePoint"},"app__schemas__public__indexing__SitemapStatsPublic":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SitemapEntry"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass verbatim as 'cursor' to fetch the next page. Absent on the last page."},"has_more":{"type":"boolean","title":"Has More","default":false},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total matching rows. Returned only where counting is cheap; absent otherwise."},"domain_id":{"type":"string","format":"uuid","title":"Domain Id"},"gsc_linked":{"type":"boolean","title":"Gsc Linked","description":"False when no GSC property is enrolled for this domain's indexing capacity."},"indexed_reporting":{"type":"string","enum":["reported","not_reported_by_google","no_linked_property","no_sitemaps"],"title":"Indexed Reporting","description":"Machine-readable reason why total_indexed, index_coverage_pct and every sitemap's indexed_urls are null, when they are. 'reported' means Search Console did report an indexed count and those fields are populated - not null. 'not_reported_by_google' means Search Console reported no indexed count for any enrolled sitemap - the signature of a figure it never computed for this property, not evidence that nothing is indexed. 'no_linked_property' means no GSC property is enrolled for this domain's indexing capacity (gsc_linked is false), so there was nothing to ask. 'no_sitemaps' means a GSC property IS enrolled (gsc_linked is true) but it has no sitemap on record at all - there was nothing for Search Console to report indexed counts against, which is a different, earlier problem than 'not_reported_by_google' (sitemaps exist; Google just has not computed a count for them yet)."},"total_submitted":{"type":"integer","title":"Total Submitted"},"total_indexed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Indexed","description":"Sum of the per-sitemap indexed counts, or null when Search Console reported none of them. Same reasoning as index_coverage_pct below - see indexed_reporting for the machine-readable reason."},"total_errors":{"type":"integer","title":"Total Errors"},"total_warnings":{"type":"integer","title":"Total Warnings"},"index_coverage_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Index Coverage Pct","description":"total_indexed / total_submitted * 100, or null when Search Console reported no indexed count for any sitemap. Google routinely omits that figure for large sitemaps and for properties it has not computed; rendering the absence as 0.0 would assert 'none of your pages are indexed', which contradicts the URL-inspection totals on the same domain. When null, read indexed counts from the issue summary instead, and read indexed_reporting for why this is null."},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Most recent date Search Console reported stats for any enrolled sitemap - the max of every row's last_checked, over the full enrolled set, not just this page. Named to match GET .../sitemap/stats's own data_through so the two can be compared directly: they routinely disagree because the sitemap scanner and Search Console's own reporting run on different schedules, not because either count is wrong. Null when no sitemap has ever been checked."}},"type":"object","required":["items","domain_id","gsc_linked","indexed_reporting","total_submitted","total_errors","total_warnings"],"title":"SitemapStatsPublic"},"app__schemas__public__indexing__TimelinePoint":{"properties":{"date":{"type":"string","format":"date","title":"Date"},"coverage_states":{"additionalProperties":{"type":"integer"},"type":"object","title":"Coverage States","description":"URL count per coverage_state as of this date, carried forward from the last inspection at or before it."}},"type":"object","required":["date","coverage_states"],"title":"TimelinePoint"},"app__schemas__public__sitemap__SitemapStatsPublic":{"properties":{"total_urls_discovered":{"type":"integer","title":"Total Urls Discovered"},"last_scan_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Scan At"},"last_scan_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Scan Status","description":"'success', 'failed', or 'in_progress'."},"last_scan_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Scan Error"},"last_scan_http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Scan Http Status"},"can_initiate_scan":{"type":"boolean","title":"Can Initiate Scan","description":"Whether nothing is currently blocking a new scan from starting (mirrors rate_limit_info.can_scan) - true does NOT mean this API can start one. Starting a scan is a dashboard-only action today (the domain's settings page 'Scan Now' button); no route on this API and no MCP tool can trigger it."},"rate_limit_info":{"$ref":"#/components/schemas/SitemapRateLimitPublic"},"data_through":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Data Through","description":"Date of the most recent completed sitemap scan (the date portion of last_scan_at). Named to match GET .../indexing/sitemaps's own data_through so the two can be compared directly - total_urls_discovered here and total_submitted there routinely disagree because the sitemap scanner and Search Console's own reporting run on independent schedules, not because either count is wrong. Null when no scan has ever completed."},"scan_age_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Scan Age Days","description":"Days between data_through and today, computed server-side so a caller does not have to do date arithmetic on last_scan_at to see how stale this scan is. Null when no scan has ever completed."}},"type":"object","required":["total_urls_discovered","can_initiate_scan","rate_limit_info"],"title":"SitemapStatsPublic"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"},"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key (`vsk_live_...`) or an OAuth 2.1 access token. Required scopes are listed per operation."}}},"servers":[{"url":"https://app.vitalsentinel.com","description":"The application host that serves the public API."}],"security":[{"bearerAuth":[]}]}