From 4235f58ed54db7984301131370e138287c7e2c93 Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Thu, 15 Oct 2026 18:34:34 +0000
Subject: [PATCH 1/2] [fern-generated] Update SDK
Generated by Fern
CLI Version: unknown
Generators:
- fernapi/fern-python-sdk: 4.63.4
---
.fern/metadata.json | 6 +-
.fern/replay.lock | 10 +
.fernignore | 5 +-
.gitattributes | 1 +
poetry.lock | 134 +-
pyproject.toml | 2 +-
reference.md | 11068 +++++++++++++---
src/webflow/__init__.py | 738 +-
src/webflow/client.py | 81 +-
src/webflow/core/client_wrapper.py | 4 +-
src/webflow/errors/__init__.py | 3 +
.../errors/service_unavailable_error.py | 11 +
src/webflow/resources/__init__.py | 36 +-
src/webflow/resources/analyze/__init__.py | 70 +
src/webflow/resources/analyze/client.py | 82 +
src/webflow/resources/analyze/raw_client.py | 13 +
.../resources/analyze/resources/__init__.py | 69 +
.../analyze/resources/goals/__init__.py | 34 +
.../analyze/resources/goals/client.py | 414 +
.../analyze/resources/goals/raw_client.py | 664 +
.../analyze/resources/goals/types/__init__.py | 36 +
...ls_goal_performance_request_device_type.py | 5 +
.../analyze/resources/reports/__init__.py | 61 +
.../analyze/resources/reports/client.py | 2336 ++++
.../analyze/resources/reports/raw_client.py | 3233 +++++
.../resources/reports/types/__init__.py | 59 +
...owser_window_height_request_device_type.py | 5 +
.../reports_page_flow_request_device_type.py | 5 +
...eports_scroll_depth_request_device_type.py | 5 +
...eports_time_on_page_request_device_type.py | 5 +
...orts_top_dimensions_request_device_type.py | 5 +
.../reports_top_events_request_device_type.py | 5 +
.../reports_top_pages_request_device_type.py | 5 +
.../reports_traffic_request_device_type.py | 5 +
src/webflow/resources/assets/client.py | 106 +-
src/webflow/resources/assets/raw_client.py | 90 +-
src/webflow/resources/collections/__init__.py | 33 +
src/webflow/resources/collections/client.py | 143 +
.../resources/collections/raw_client.py | 277 +
.../collections/resources/__init__.py | 33 +
.../resources/fields/raw_client.py | 6 -
.../collections/resources/items/__init__.py | 33 +
.../collections/resources/items/client.py | 964 +-
.../collections/resources/items/raw_client.py | 1110 +-
.../resources/items/types/__init__.py | 33 +
...ms_delete_items_live_request_items_item.py | 2 +-
.../items_delete_items_request_items_item.py | 2 +-
.../types/items_insert_items_response.py | 23 +
...ms_list_items_live_request_filter_value.py | 36 +
..._items_live_request_filter_value_exists.py | 5 +
...list_items_live_request_filter_value_in.py | 5 +
...ist_items_live_request_filter_value_nin.py | 5 +
.../items_list_items_live_request_sort_by.py | 4 +-
...tems_list_items_live_request_sort_value.py | 5 +
.../items_list_items_request_filter_value.py | 36 +
..._list_items_request_filter_value_exists.py | 5 +
...tems_list_items_request_filter_value_in.py | 5 +
...ems_list_items_request_filter_value_nin.py | 5 +
.../types/items_list_items_request_sort_by.py | 4 +-
.../items_list_items_request_sort_value.py | 5 +
.../resources/items/types/multiple_items.py | 2 +-
.../items/types/multiple_live_items.py | 2 +-
src/webflow/resources/components/client.py | 80 +-
.../resources/components/raw_client.py | 76 +-
.../resources/custom_fonts/__init__.py | 58 +
src/webflow/resources/custom_fonts/client.py | 1047 ++
.../resources/custom_fonts/raw_client.py | 2277 ++++
.../resources/custom_fonts/types/__init__.py | 58 +
...om_font_batch_create_request_items_item.py | 64 +
..._create_request_items_item_font_display.py | 7 +
...om_font_batch_delete_request_items_item.py | 22 +
...ustom_fonts_create_request_font_display.py | 7 +
.../types/custom_fonts_get_response.py | 24 +
...ustom_fonts_update_request_font_display.py | 7 +
.../types/custom_fonts_update_response.py | 24 +
src/webflow/resources/optimize/__init__.py | 34 +
src/webflow/resources/optimize/client.py | 145 +
src/webflow/resources/optimize/raw_client.py | 241 +
.../resources/optimize/resources/__init__.py | 34 +
.../resources/optimizations/__init__.py | 34 +
.../resources/optimizations/client.py | 794 ++
.../resources/optimizations/raw_client.py | 1355 ++
.../optimizations/resources/__init__.py | 34 +
.../resources/dimensions/__init__.py | 4 +
.../resources/dimensions/client.py | 387 +
.../resources/dimensions/raw_client.py | 671 +
.../optimizations/resources/goals/__init__.py | 4 +
.../optimizations/resources/goals/client.py | 186 +
.../resources/goals/raw_client.py | 326 +
src/webflow/resources/pages/client.py | 108 +-
src/webflow/resources/pages/raw_client.py | 86 +-
.../pages/resources/scripts/client.py | 8 +
.../pages/resources/scripts/raw_client.py | 14 +-
src/webflow/resources/scripts/client.py | 12 +
src/webflow/resources/scripts/raw_client.py | 12 +
src/webflow/resources/sites/__init__.py | 16 +-
src/webflow/resources/sites/client.py | 85 +-
src/webflow/resources/sites/raw_client.py | 28 +-
.../resources/sites/resources/__init__.py | 23 +-
.../resources/sites/resources/aeo/__init__.py | 55 +
.../resources/sites/resources/aeo/client.py | 101 +
.../sites/resources/aeo/raw_client.py | 13 +
.../sites/resources/aeo/resources/__init__.py | 50 +
.../resources/prompt_executions/__init__.py | 4 +
.../aeo/resources/prompt_executions/client.py | 350 +
.../resources/prompt_executions/raw_client.py | 603 +
.../aeo/resources/prompts/__init__.py | 34 +
.../resources/aeo/resources/prompts/client.py | 181 +
.../aeo/resources/prompts/raw_client.py | 318 +
.../aeo/resources/prompts/types/__init__.py | 34 +
.../types/prompts_list_request_status.py | 5 +
.../aeo/resources/reports/__init__.py | 37 +
.../resources/aeo/resources/reports/client.py | 1866 +++
.../aeo/resources/reports/raw_client.py | 3083 +++++
.../aeo/resources/reports/types/__init__.py | 38 +
...reports_citation_rate_request_breakdown.py | 5 +
..._visibility_breakdown_request_breakdown.py | 5 +
.../sites/resources/comments/client.py | 254 +-
.../sites/resources/comments/raw_client.py | 514 +-
.../sites/resources/google_tag/__init__.py | 4 +
.../sites/resources/google_tag/client.py | 388 +
.../sites/resources/google_tag/raw_client.py | 863 ++
.../sites/resources/members/__init__.py | 34 +
.../sites/resources/members/client.py | 758 ++
.../sites/resources/members/raw_client.py | 1728 +++
.../sites/resources/members/types/__init__.py | 36 +
...rant_site_member_request_body_site_role.py | 7 +
.../sites/resources/redirects/raw_client.py | 12 -
.../sites/resources/robots_txt/raw_client.py | 18 -
.../sites/resources/scripts/client.py | 12 +
.../sites/resources/scripts/raw_client.py | 18 +-
src/webflow/resources/sites/types/__init__.py | 8 +-
.../sites/types/sites_publish_response.py | 6 +
.../sites_publish_response_publish_scope.py | 5 +
src/webflow/resources/token/client.py | 8 +-
src/webflow/resources/token/raw_client.py | 8 +-
src/webflow/resources/webhooks/client.py | 4 +-
src/webflow/resources/webhooks/raw_client.py | 10 +-
src/webflow/resources/workspaces/__init__.py | 18 +
src/webflow/resources/workspaces/client.py | 93 +
.../resources/workspaces/raw_client.py | 193 +
.../workspaces/resources/__init__.py | 21 +-
.../workspaces/resources/members/__init__.py | 52 +
.../workspaces/resources/members/client.py | 764 ++
.../resources/members/raw_client.py | 1553 +++
.../resources/members/types/__init__.py | 54 +
...er_roles_request_body_default_site_role.py | 7 +
...orkspace_member_roles_request_body_role.py | 5 +
...members_list_members_request_roles_item.py | 7 +
...ber_role_request_body_default_site_role.py | 7 +
...workspace_member_role_request_body_role.py | 5 +
src/webflow/types/__init__.py | 728 +-
src/webflow/types/aeo_answer_engine.py | 31 +
src/webflow/types/aeo_answer_engine_key.py | 5 +
.../types/aeo_answer_engines_filter.py | 7 +
src/webflow/types/aeo_bot_category.py | 5 +
src/webflow/types/aeo_bot_provider.py | 22 +
src/webflow/types/aeo_bot_traffic_filter.py | 35 +
src/webflow/types/aeo_bot_traffic_page.py | 33 +
.../types/aeo_bot_traffic_pages_response.py | 33 +
src/webflow/types/aeo_bot_traffic_response.py | 37 +
src/webflow/types/aeo_brand_names.py | 5 +
src/webflow/types/aeo_citation_filter.py | 38 +
.../types/aeo_citation_rate_response.py | 44 +
.../aeo_citation_rate_response_breakdown.py | 5 +
.../aeo_citation_rate_response_data_item.py | 39 +
src/webflow/types/aeo_citations_response.py | 53 +
.../types/aeo_citations_response_data_item.py | 53 +
.../types/aeo_cited_domain_paths_response.py | 38 +
src/webflow/types/aeo_cited_path.py | 30 +
.../aeo_competitor_citations_response.py | 43 +
...competitor_citations_response_data_item.py | 38 +
src/webflow/types/aeo_owned_cited_path.py | 35 +
src/webflow/types/aeo_page.py | 43 +
.../types/aeo_page_citation_share_response.py | 47 +
..._page_citation_share_response_data_item.py | 33 +
...aeo_page_citation_share_response_filter.py | 30 +
.../aeo_page_citation_share_response_page.py | 41 +
src/webflow/types/aeo_prompt.py | 80 +
src/webflow/types/aeo_prompt_execution.py | 99 +
.../aeo_prompt_execution_list_response.py | 34 +
.../aeo_prompt_execution_prompt_status.py | 7 +
.../types/aeo_prompt_execution_status.py | 5 +
.../types/aeo_prompt_execution_topic.py | 31 +
src/webflow/types/aeo_prompt_ids_filter.py | 5 +
src/webflow/types/aeo_prompt_label.py | 31 +
src/webflow/types/aeo_prompt_list_response.py | 34 +
src/webflow/types/aeo_prompt_status.py | 5 +
src/webflow/types/aeo_prompt_topic.py | 31 +
src/webflow/types/aeo_timeseries.py | 37 +
.../aeo_timeseries_granularity_period.py | 5 +
.../types/aeo_top_cited_domains_response.py | 33 +
...eo_top_cited_domains_response_data_item.py | 46 +
src/webflow/types/aeo_topic_engine_filter.py | 34 +
src/webflow/types/aeo_topic_ids_filter.py | 5 +
src/webflow/types/aeo_topic_label.py | 31 +
.../aeo_visibility_breakdown_response.py | 50 +
...visibility_breakdown_response_breakdown.py | 5 +
...visibility_breakdown_response_data_item.py | 35 +
src/webflow/types/aeo_visibility_filter.py | 36 +
src/webflow/types/aeo_visibility_response.py | 45 +
.../aeo_visibility_response_data_item.py | 30 +
src/webflow/types/aeo_visibility_score.py | 3 +
src/webflow/types/aeo_window.py | 35 +
src/webflow/types/analyze_bucket_time_zone.py | 3 +
src/webflow/types/analyze_daily_bucketing.py | 33 +
.../types/analyze_daily_timeseries_query.py | 28 +
src/webflow/types/analyze_filter_operators.py | 46 +
.../types/analyze_time_on_page_bucketing.py | 32 +
src/webflow/types/analyze_window.py | 39 +
src/webflow/types/asset.py | 8 +
src/webflow/types/authorization.py | 5 +-
src/webflow/types/branch_created_webhook.py | 29 +
.../types/branch_created_webhook_payload.py | 49 +
src/webflow/types/branch_deleted_webhook.py | 29 +
.../types/branch_deleted_webhook_payload.py | 42 +
src/webflow/types/branch_merged_webhook.py | 29 +
.../types/branch_merged_webhook_payload.py | 42 +
.../types/browser_window_height_data_point.py | 32 +
.../types/browser_window_height_filter.py | 114 +
.../types/browser_window_height_response.py | 37 +
src/webflow/types/bulk_collection_item.py | 5 +-
.../types/collaborator_client_invite.py | 34 +
src/webflow/types/collection.py | 7 +
src/webflow/types/collection_item.py | 5 +-
.../types/collection_item_patch_single.py | 5 +-
.../types/collection_item_post_single.py | 5 +-
.../types/collection_item_with_id_input.py | 5 +-
src/webflow/types/comment_payload.py | 14 +-
src/webflow/types/comment_payload_type.py | 5 +
src/webflow/types/component_property.py | 44 +-
src/webflow/types/component_property_text.py | 45 +
.../types/component_property_text_type.py | 5 +
src/webflow/types/component_property_type.py | 5 -
src/webflow/types/custom_font.py | 72 +
src/webflow/types/custom_font_axis.py | 52 +
.../custom_font_batch_create_response.py | 33 +
...ont_batch_create_response_created_item.py} | 9 +-
..._font_batch_create_response_failed_item.py | 39 +
.../custom_font_batch_delete_response.py | 26 +
...font_batch_delete_response_deleted_item.py | 22 +
..._font_batch_delete_response_failed_item.py | 32 +
.../types/custom_font_create_response.py | 30 +
src/webflow/types/custom_font_font_display.py | 5 +
src/webflow/types/custom_font_format.py | 7 +
src/webflow/types/custom_font_upload.py | 40 +
.../types/custom_font_upload_fields.py | 94 +
src/webflow/types/custom_fonts.py | 30 +
src/webflow/types/error_code.py | 69 +
src/webflow/types/field_group.py | 38 +
...field_validations_additional_properties.py | 8 +-
src/webflow/types/form_submission.py | 8 +
.../types/form_submission_trigger_payload.py | 8 +
src/webflow/types/goal.py | 90 +
src/webflow/types/goal_category.py | 5 +
src/webflow/types/goal_integration_source.py | 5 +
.../types/goal_performance_data_point.py | 32 +
src/webflow/types/goal_performance_filter.py | 114 +
.../types/goal_performance_response.py | 36 +
src/webflow/types/goal_scope.py | 5 +
src/webflow/types/goal_type.py | 5 +
src/webflow/types/goal_value_type.py | 5 +
src/webflow/types/goals_response.py | 27 +
src/webflow/types/google_tag_id.py | 42 +
src/webflow/types/google_tag_ids.py | 30 +
src/webflow/types/insert_collection_item.py | 66 +
.../insert_collection_item_field_data.py | 27 +
src/webflow/types/inserted_collection_item.py | 69 +
.../inserted_collection_item_field_data.py | 27 +
.../invite_workspace_member_request_body.py | 9 +
...ody_default_site_role_default_site_role.py | 7 +
...t_body_default_site_role_workspace_role.py | 7 +
...ber_request_body_email_site_access_item.py | 37 +
...t_body_email_site_access_item_site_role.py | 7 +
...ember_request_body_one_site_access_item.py | 37 +
...est_body_one_site_access_item_site_role.py | 7 +
..._member_request_body_one_workspace_role.py | 7 +
.../invite_workspace_member_response_body.py | 53 +
...e_workspace_member_response_body_active.py | 20 +
...ace_audit_log_item_actor.py => invited.py} | 6 +-
...tems_list_items_live_request_created_on.py | 28 +
...ms_list_items_live_request_last_updated.py | 28 +
.../items_list_items_request_created_on.py | 28 +
.../items_list_items_request_last_updated.py | 28 +
src/webflow/types/optimization.py | 66 +
...optimization_audience_insights_response.py | 58 +
.../optimization_dimension_values_response.py | 54 +
.../types/optimization_dimensions_response.py | 37 +
.../types/optimization_goals_response.py | 47 +
src/webflow/types/optimization_interval.py | 40 +
.../types/optimization_list_response.py | 44 +
src/webflow/types/optimization_status.py | 5 +
.../types/optimization_summary_response.py | 50 +
.../types/optimization_trend_response.py | 60 +
src/webflow/types/optimization_type.py | 5 +
src/webflow/types/optimize_applied_filter.py | 7 +
.../types/optimize_applied_filter_entry.py | 38 +
.../types/optimize_audience_segment.py | 80 +
.../types/optimize_audience_variation.py | 37 +
src/webflow/types/optimize_current_goal.py | 49 +
src/webflow/types/optimize_dimension.py | 31 +
src/webflow/types/optimize_dimension_value.py | 39 +
src/webflow/types/optimize_filter.py | 7 +
.../types/optimize_filter_operators.py | 36 +
src/webflow/types/optimize_goal.py | 54 +
.../types/optimize_goal_counting_method.py | 5 +
.../types/optimize_goal_metric_type.py | 5 +
src/webflow/types/optimize_goal_scope.py | 5 +
src/webflow/types/optimize_goal_source.py | 5 +
.../optimize_non_webflow_site_resource.py | 38 +
src/webflow/types/optimize_reference.py | 37 +
.../types/optimize_reference_list_response.py | 27 +
.../types/optimize_reference_resource.py | 64 +
src/webflow/types/optimize_report_goal.py | 31 +
.../types/optimize_report_interval_end.py | 5 +
.../types/optimize_report_interval_start.py | 5 +
src/webflow/types/optimize_trend_direction.py | 5 +
.../types/optimize_trend_granularity.py | 5 +
src/webflow/types/optimize_trend_point.py | 33 +
.../types/optimize_variation_comparison.py | 53 +
.../types/optimize_variation_result.py | 69 +
.../types/optimize_webflow_site_resource.py | 37 +
.../types/page_created_webhook_payload.py | 20 +
.../types/page_deleted_webhook_payload.py | 20 +
src/webflow/types/page_flow_direction.py | 5 +
src/webflow/types/page_flow_filter.py | 114 +
src/webflow/types/page_flow_metric_scope.py | 5 +
src/webflow/types/page_flow_response.py | 71 +
src/webflow/types/page_flow_row.py | 60 +
.../page_metadata_updated_webhook_payload.py | 20 +
src/webflow/types/person_actor.py | 27 +
src/webflow/types/scroll_depth_data_point.py | 37 +
src/webflow/types/scroll_depth_filter.py | 114 +
src/webflow/types/scroll_depth_response.py | 37 +
src/webflow/types/setting_change.py | 3 +-
src/webflow/types/site_activity_log_item.py | 31 +
.../site_activity_log_item_actor_type.py | 5 +
.../types/site_activity_log_item_source.py | 5 +
src/webflow/types/site_member.py | 45 +
src/webflow/types/site_member_roles.py | 43 +
src/webflow/types/site_member_roles_site.py | 18 +
.../types/site_member_roles_workspace.py | 7 +
src/webflow/types/site_members_response.py | 29 +
.../types/site_members_response_pagination.py | 37 +
src/webflow/types/site_publish_payload.py | 13 +-
.../site_publish_payload_publish_scope.py | 5 +
src/webflow/types/time_on_page_data_point.py | 38 +
src/webflow/types/time_on_page_filter.py | 114 +
.../types/time_on_page_granularity_period.py | 5 +
.../types/time_on_page_metric_scope.py | 5 +
src/webflow/types/time_on_page_response.py | 45 +
.../types/time_on_page_timeseries_query.py | 32 +
src/webflow/types/token_actor.py | 27 +
src/webflow/types/top_dimensions_dimension.py | 24 +
src/webflow/types/top_dimensions_filter.py | 114 +
.../types/top_dimensions_metric_scope.py | 5 +
src/webflow/types/top_dimensions_response.py | 50 +
src/webflow/types/top_dimensions_row.py | 41 +
.../types/top_events_cms_context_entry.py | 32 +
.../top_events_component_context_entry.py | 34 +
src/webflow/types/top_events_filter.py | 85 +
src/webflow/types/top_events_response.py | 44 +
src/webflow/types/top_events_row.py | 93 +
.../types/top_events_timeseries_point.py | 34 +
src/webflow/types/top_pages_filter.py | 114 +
src/webflow/types/top_pages_response.py | 48 +
src/webflow/types/top_pages_row.py | 76 +
src/webflow/types/top_pages_sort_by.py | 5 +
.../types/top_pages_timeseries_point.py | 39 +
src/webflow/types/traffic_data_point.py | 32 +
src/webflow/types/traffic_filter.py | 114 +
src/webflow/types/traffic_metric_scope.py | 5 +
src/webflow/types/traffic_response.py | 45 +
src/webflow/types/trigger_type.py | 3 +
.../update_site_member_role_request_body.py | 34 +
...site_member_role_request_body_site_role.py | 7 +
src/webflow/types/with_default_site_role.py | 48 +
src/webflow/types/with_site_access.py | 36 +
src/webflow/types/workspace.py | 32 +
.../types/workspace_audit_log_actor.py | 52 +
src/webflow/types/workspace_audit_log_item.py | 4 +-
..._log_item_payload_setting_change_method.py | 5 -
...log_item_payload_site_membership_method.py | 2 +-
...tem_payload_workspace_invitation_method.py | 2 +-
...tem_payload_workspace_membership_method.py | 2 +-
.../workspace_bulk_role_change_response.py | 33 +
..._bulk_role_change_response_results_item.py | 40 +
...role_change_response_results_item_error.py | 34 +
...change_response_results_item_error_code.py | 16 +
...le_change_response_results_item_outcome.py | 5 +
..._role_change_response_results_item_type.py | 5 +
...space_bulk_role_change_response_summary.py | 31 +
...nvitation_audit_log_item_event_sub_type.py | 3 +-
src/webflow/types/workspace_invite.py | 75 +
.../workspace_invite_default_site_role.py | 18 +
src/webflow/types/workspace_invite_role.py | 7 +
src/webflow/types/workspace_invite_status.py | 5 +
src/webflow/types/workspace_member.py | 55 +
src/webflow/types/workspace_member_roles.py | 33 +
.../types/workspace_member_roles_site.py | 18 +
.../types/workspace_member_roles_workspace.py | 7 +
.../types/workspace_members_response.py | 35 +
.../workspace_members_response_pagination.py | 37 +
src/webflow/types/workspaces.py | 20 +
404 files changed, 51650 insertions(+), 1880 deletions(-)
create mode 100644 .fern/replay.lock
create mode 100644 .gitattributes
create mode 100644 src/webflow/errors/service_unavailable_error.py
create mode 100644 src/webflow/resources/analyze/__init__.py
create mode 100644 src/webflow/resources/analyze/client.py
create mode 100644 src/webflow/resources/analyze/raw_client.py
create mode 100644 src/webflow/resources/analyze/resources/__init__.py
create mode 100644 src/webflow/resources/analyze/resources/goals/__init__.py
create mode 100644 src/webflow/resources/analyze/resources/goals/client.py
create mode 100644 src/webflow/resources/analyze/resources/goals/raw_client.py
create mode 100644 src/webflow/resources/analyze/resources/goals/types/__init__.py
create mode 100644 src/webflow/resources/analyze/resources/goals/types/goals_goal_performance_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/__init__.py
create mode 100644 src/webflow/resources/analyze/resources/reports/client.py
create mode 100644 src/webflow/resources/analyze/resources/reports/raw_client.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/__init__.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_browser_window_height_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_page_flow_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_scroll_depth_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_time_on_page_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_top_dimensions_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_top_events_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_top_pages_request_device_type.py
create mode 100644 src/webflow/resources/analyze/resources/reports/types/reports_traffic_request_device_type.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_insert_items_response.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_exists.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_in.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_nin.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_value.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_exists.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_in.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_nin.py
create mode 100644 src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_value.py
create mode 100644 src/webflow/resources/custom_fonts/__init__.py
create mode 100644 src/webflow/resources/custom_fonts/client.py
create mode 100644 src/webflow/resources/custom_fonts/raw_client.py
create mode 100644 src/webflow/resources/custom_fonts/types/__init__.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item_font_display.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_font_batch_delete_request_items_item.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_fonts_create_request_font_display.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_fonts_get_response.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_fonts_update_request_font_display.py
create mode 100644 src/webflow/resources/custom_fonts/types/custom_fonts_update_response.py
create mode 100644 src/webflow/resources/optimize/__init__.py
create mode 100644 src/webflow/resources/optimize/client.py
create mode 100644 src/webflow/resources/optimize/raw_client.py
create mode 100644 src/webflow/resources/optimize/resources/__init__.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/__init__.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/client.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/raw_client.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/__init__.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/dimensions/__init__.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/dimensions/client.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/dimensions/raw_client.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/goals/__init__.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/goals/client.py
create mode 100644 src/webflow/resources/optimize/resources/optimizations/resources/goals/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompt_executions/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompt_executions/client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompt_executions/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompts/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompts/client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompts/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompts/types/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/prompts/types/prompts_list_request_status.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/types/__init__.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_citation_rate_request_breakdown.py
create mode 100644 src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_visibility_breakdown_request_breakdown.py
create mode 100644 src/webflow/resources/sites/resources/google_tag/__init__.py
create mode 100644 src/webflow/resources/sites/resources/google_tag/client.py
create mode 100644 src/webflow/resources/sites/resources/google_tag/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/members/__init__.py
create mode 100644 src/webflow/resources/sites/resources/members/client.py
create mode 100644 src/webflow/resources/sites/resources/members/raw_client.py
create mode 100644 src/webflow/resources/sites/resources/members/types/__init__.py
create mode 100644 src/webflow/resources/sites/resources/members/types/grant_site_member_request_body_site_role.py
create mode 100644 src/webflow/resources/sites/types/sites_publish_response_publish_scope.py
create mode 100644 src/webflow/resources/workspaces/resources/members/__init__.py
create mode 100644 src/webflow/resources/workspaces/resources/members/client.py
create mode 100644 src/webflow/resources/workspaces/resources/members/raw_client.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/__init__.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_default_site_role.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_role.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/members_list_members_request_roles_item.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_default_site_role.py
create mode 100644 src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_role.py
create mode 100644 src/webflow/types/aeo_answer_engine.py
create mode 100644 src/webflow/types/aeo_answer_engine_key.py
create mode 100644 src/webflow/types/aeo_answer_engines_filter.py
create mode 100644 src/webflow/types/aeo_bot_category.py
create mode 100644 src/webflow/types/aeo_bot_provider.py
create mode 100644 src/webflow/types/aeo_bot_traffic_filter.py
create mode 100644 src/webflow/types/aeo_bot_traffic_page.py
create mode 100644 src/webflow/types/aeo_bot_traffic_pages_response.py
create mode 100644 src/webflow/types/aeo_bot_traffic_response.py
create mode 100644 src/webflow/types/aeo_brand_names.py
create mode 100644 src/webflow/types/aeo_citation_filter.py
create mode 100644 src/webflow/types/aeo_citation_rate_response.py
create mode 100644 src/webflow/types/aeo_citation_rate_response_breakdown.py
create mode 100644 src/webflow/types/aeo_citation_rate_response_data_item.py
create mode 100644 src/webflow/types/aeo_citations_response.py
create mode 100644 src/webflow/types/aeo_citations_response_data_item.py
create mode 100644 src/webflow/types/aeo_cited_domain_paths_response.py
create mode 100644 src/webflow/types/aeo_cited_path.py
create mode 100644 src/webflow/types/aeo_competitor_citations_response.py
create mode 100644 src/webflow/types/aeo_competitor_citations_response_data_item.py
create mode 100644 src/webflow/types/aeo_owned_cited_path.py
create mode 100644 src/webflow/types/aeo_page.py
create mode 100644 src/webflow/types/aeo_page_citation_share_response.py
create mode 100644 src/webflow/types/aeo_page_citation_share_response_data_item.py
create mode 100644 src/webflow/types/aeo_page_citation_share_response_filter.py
create mode 100644 src/webflow/types/aeo_page_citation_share_response_page.py
create mode 100644 src/webflow/types/aeo_prompt.py
create mode 100644 src/webflow/types/aeo_prompt_execution.py
create mode 100644 src/webflow/types/aeo_prompt_execution_list_response.py
create mode 100644 src/webflow/types/aeo_prompt_execution_prompt_status.py
create mode 100644 src/webflow/types/aeo_prompt_execution_status.py
create mode 100644 src/webflow/types/aeo_prompt_execution_topic.py
create mode 100644 src/webflow/types/aeo_prompt_ids_filter.py
create mode 100644 src/webflow/types/aeo_prompt_label.py
create mode 100644 src/webflow/types/aeo_prompt_list_response.py
create mode 100644 src/webflow/types/aeo_prompt_status.py
create mode 100644 src/webflow/types/aeo_prompt_topic.py
create mode 100644 src/webflow/types/aeo_timeseries.py
create mode 100644 src/webflow/types/aeo_timeseries_granularity_period.py
create mode 100644 src/webflow/types/aeo_top_cited_domains_response.py
create mode 100644 src/webflow/types/aeo_top_cited_domains_response_data_item.py
create mode 100644 src/webflow/types/aeo_topic_engine_filter.py
create mode 100644 src/webflow/types/aeo_topic_ids_filter.py
create mode 100644 src/webflow/types/aeo_topic_label.py
create mode 100644 src/webflow/types/aeo_visibility_breakdown_response.py
create mode 100644 src/webflow/types/aeo_visibility_breakdown_response_breakdown.py
create mode 100644 src/webflow/types/aeo_visibility_breakdown_response_data_item.py
create mode 100644 src/webflow/types/aeo_visibility_filter.py
create mode 100644 src/webflow/types/aeo_visibility_response.py
create mode 100644 src/webflow/types/aeo_visibility_response_data_item.py
create mode 100644 src/webflow/types/aeo_visibility_score.py
create mode 100644 src/webflow/types/aeo_window.py
create mode 100644 src/webflow/types/analyze_bucket_time_zone.py
create mode 100644 src/webflow/types/analyze_daily_bucketing.py
create mode 100644 src/webflow/types/analyze_daily_timeseries_query.py
create mode 100644 src/webflow/types/analyze_filter_operators.py
create mode 100644 src/webflow/types/analyze_time_on_page_bucketing.py
create mode 100644 src/webflow/types/analyze_window.py
create mode 100644 src/webflow/types/branch_created_webhook.py
create mode 100644 src/webflow/types/branch_created_webhook_payload.py
create mode 100644 src/webflow/types/branch_deleted_webhook.py
create mode 100644 src/webflow/types/branch_deleted_webhook_payload.py
create mode 100644 src/webflow/types/branch_merged_webhook.py
create mode 100644 src/webflow/types/branch_merged_webhook_payload.py
create mode 100644 src/webflow/types/browser_window_height_data_point.py
create mode 100644 src/webflow/types/browser_window_height_filter.py
create mode 100644 src/webflow/types/browser_window_height_response.py
create mode 100644 src/webflow/types/collaborator_client_invite.py
create mode 100644 src/webflow/types/comment_payload_type.py
create mode 100644 src/webflow/types/component_property_text.py
create mode 100644 src/webflow/types/component_property_text_type.py
delete mode 100644 src/webflow/types/component_property_type.py
create mode 100644 src/webflow/types/custom_font.py
create mode 100644 src/webflow/types/custom_font_axis.py
create mode 100644 src/webflow/types/custom_font_batch_create_response.py
rename src/webflow/types/{field_validations_additional_properties_additional_properties.py => custom_font_batch_create_response_created_item.py} (63%)
create mode 100644 src/webflow/types/custom_font_batch_create_response_failed_item.py
create mode 100644 src/webflow/types/custom_font_batch_delete_response.py
create mode 100644 src/webflow/types/custom_font_batch_delete_response_deleted_item.py
create mode 100644 src/webflow/types/custom_font_batch_delete_response_failed_item.py
create mode 100644 src/webflow/types/custom_font_create_response.py
create mode 100644 src/webflow/types/custom_font_font_display.py
create mode 100644 src/webflow/types/custom_font_format.py
create mode 100644 src/webflow/types/custom_font_upload.py
create mode 100644 src/webflow/types/custom_font_upload_fields.py
create mode 100644 src/webflow/types/custom_fonts.py
create mode 100644 src/webflow/types/field_group.py
create mode 100644 src/webflow/types/goal.py
create mode 100644 src/webflow/types/goal_category.py
create mode 100644 src/webflow/types/goal_integration_source.py
create mode 100644 src/webflow/types/goal_performance_data_point.py
create mode 100644 src/webflow/types/goal_performance_filter.py
create mode 100644 src/webflow/types/goal_performance_response.py
create mode 100644 src/webflow/types/goal_scope.py
create mode 100644 src/webflow/types/goal_type.py
create mode 100644 src/webflow/types/goal_value_type.py
create mode 100644 src/webflow/types/goals_response.py
create mode 100644 src/webflow/types/google_tag_id.py
create mode 100644 src/webflow/types/google_tag_ids.py
create mode 100644 src/webflow/types/insert_collection_item.py
create mode 100644 src/webflow/types/insert_collection_item_field_data.py
create mode 100644 src/webflow/types/inserted_collection_item.py
create mode 100644 src/webflow/types/inserted_collection_item_field_data.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_default_site_role_default_site_role.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_default_site_role_workspace_role.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_email_site_access_item.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_email_site_access_item_site_role.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_one_site_access_item.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_one_site_access_item_site_role.py
create mode 100644 src/webflow/types/invite_workspace_member_request_body_one_workspace_role.py
create mode 100644 src/webflow/types/invite_workspace_member_response_body.py
create mode 100644 src/webflow/types/invite_workspace_member_response_body_active.py
rename src/webflow/types/{workspace_audit_log_item_actor.py => invited.py} (78%)
create mode 100644 src/webflow/types/items_list_items_live_request_created_on.py
create mode 100644 src/webflow/types/items_list_items_live_request_last_updated.py
create mode 100644 src/webflow/types/items_list_items_request_created_on.py
create mode 100644 src/webflow/types/items_list_items_request_last_updated.py
create mode 100644 src/webflow/types/optimization.py
create mode 100644 src/webflow/types/optimization_audience_insights_response.py
create mode 100644 src/webflow/types/optimization_dimension_values_response.py
create mode 100644 src/webflow/types/optimization_dimensions_response.py
create mode 100644 src/webflow/types/optimization_goals_response.py
create mode 100644 src/webflow/types/optimization_interval.py
create mode 100644 src/webflow/types/optimization_list_response.py
create mode 100644 src/webflow/types/optimization_status.py
create mode 100644 src/webflow/types/optimization_summary_response.py
create mode 100644 src/webflow/types/optimization_trend_response.py
create mode 100644 src/webflow/types/optimization_type.py
create mode 100644 src/webflow/types/optimize_applied_filter.py
create mode 100644 src/webflow/types/optimize_applied_filter_entry.py
create mode 100644 src/webflow/types/optimize_audience_segment.py
create mode 100644 src/webflow/types/optimize_audience_variation.py
create mode 100644 src/webflow/types/optimize_current_goal.py
create mode 100644 src/webflow/types/optimize_dimension.py
create mode 100644 src/webflow/types/optimize_dimension_value.py
create mode 100644 src/webflow/types/optimize_filter.py
create mode 100644 src/webflow/types/optimize_filter_operators.py
create mode 100644 src/webflow/types/optimize_goal.py
create mode 100644 src/webflow/types/optimize_goal_counting_method.py
create mode 100644 src/webflow/types/optimize_goal_metric_type.py
create mode 100644 src/webflow/types/optimize_goal_scope.py
create mode 100644 src/webflow/types/optimize_goal_source.py
create mode 100644 src/webflow/types/optimize_non_webflow_site_resource.py
create mode 100644 src/webflow/types/optimize_reference.py
create mode 100644 src/webflow/types/optimize_reference_list_response.py
create mode 100644 src/webflow/types/optimize_reference_resource.py
create mode 100644 src/webflow/types/optimize_report_goal.py
create mode 100644 src/webflow/types/optimize_report_interval_end.py
create mode 100644 src/webflow/types/optimize_report_interval_start.py
create mode 100644 src/webflow/types/optimize_trend_direction.py
create mode 100644 src/webflow/types/optimize_trend_granularity.py
create mode 100644 src/webflow/types/optimize_trend_point.py
create mode 100644 src/webflow/types/optimize_variation_comparison.py
create mode 100644 src/webflow/types/optimize_variation_result.py
create mode 100644 src/webflow/types/optimize_webflow_site_resource.py
create mode 100644 src/webflow/types/page_flow_direction.py
create mode 100644 src/webflow/types/page_flow_filter.py
create mode 100644 src/webflow/types/page_flow_metric_scope.py
create mode 100644 src/webflow/types/page_flow_response.py
create mode 100644 src/webflow/types/page_flow_row.py
create mode 100644 src/webflow/types/person_actor.py
create mode 100644 src/webflow/types/scroll_depth_data_point.py
create mode 100644 src/webflow/types/scroll_depth_filter.py
create mode 100644 src/webflow/types/scroll_depth_response.py
create mode 100644 src/webflow/types/site_activity_log_item_actor_type.py
create mode 100644 src/webflow/types/site_activity_log_item_source.py
create mode 100644 src/webflow/types/site_member.py
create mode 100644 src/webflow/types/site_member_roles.py
create mode 100644 src/webflow/types/site_member_roles_site.py
create mode 100644 src/webflow/types/site_member_roles_workspace.py
create mode 100644 src/webflow/types/site_members_response.py
create mode 100644 src/webflow/types/site_members_response_pagination.py
create mode 100644 src/webflow/types/site_publish_payload_publish_scope.py
create mode 100644 src/webflow/types/time_on_page_data_point.py
create mode 100644 src/webflow/types/time_on_page_filter.py
create mode 100644 src/webflow/types/time_on_page_granularity_period.py
create mode 100644 src/webflow/types/time_on_page_metric_scope.py
create mode 100644 src/webflow/types/time_on_page_response.py
create mode 100644 src/webflow/types/time_on_page_timeseries_query.py
create mode 100644 src/webflow/types/token_actor.py
create mode 100644 src/webflow/types/top_dimensions_dimension.py
create mode 100644 src/webflow/types/top_dimensions_filter.py
create mode 100644 src/webflow/types/top_dimensions_metric_scope.py
create mode 100644 src/webflow/types/top_dimensions_response.py
create mode 100644 src/webflow/types/top_dimensions_row.py
create mode 100644 src/webflow/types/top_events_cms_context_entry.py
create mode 100644 src/webflow/types/top_events_component_context_entry.py
create mode 100644 src/webflow/types/top_events_filter.py
create mode 100644 src/webflow/types/top_events_response.py
create mode 100644 src/webflow/types/top_events_row.py
create mode 100644 src/webflow/types/top_events_timeseries_point.py
create mode 100644 src/webflow/types/top_pages_filter.py
create mode 100644 src/webflow/types/top_pages_response.py
create mode 100644 src/webflow/types/top_pages_row.py
create mode 100644 src/webflow/types/top_pages_sort_by.py
create mode 100644 src/webflow/types/top_pages_timeseries_point.py
create mode 100644 src/webflow/types/traffic_data_point.py
create mode 100644 src/webflow/types/traffic_filter.py
create mode 100644 src/webflow/types/traffic_metric_scope.py
create mode 100644 src/webflow/types/traffic_response.py
create mode 100644 src/webflow/types/update_site_member_role_request_body.py
create mode 100644 src/webflow/types/update_site_member_role_request_body_site_role.py
create mode 100644 src/webflow/types/with_default_site_role.py
create mode 100644 src/webflow/types/with_site_access.py
create mode 100644 src/webflow/types/workspace.py
create mode 100644 src/webflow/types/workspace_audit_log_actor.py
delete mode 100644 src/webflow/types/workspace_audit_log_item_payload_setting_change_method.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_results_item.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_results_item_error.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_results_item_error_code.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_results_item_outcome.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_results_item_type.py
create mode 100644 src/webflow/types/workspace_bulk_role_change_response_summary.py
create mode 100644 src/webflow/types/workspace_invite.py
create mode 100644 src/webflow/types/workspace_invite_default_site_role.py
create mode 100644 src/webflow/types/workspace_invite_role.py
create mode 100644 src/webflow/types/workspace_invite_status.py
create mode 100644 src/webflow/types/workspace_member.py
create mode 100644 src/webflow/types/workspace_member_roles.py
create mode 100644 src/webflow/types/workspace_member_roles_site.py
create mode 100644 src/webflow/types/workspace_member_roles_workspace.py
create mode 100644 src/webflow/types/workspace_members_response.py
create mode 100644 src/webflow/types/workspace_members_response_pagination.py
create mode 100644 src/webflow/types/workspaces.py
diff --git a/.fern/metadata.json b/.fern/metadata.json
index 44a28821..b6aea628 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -1,5 +1,5 @@
{
- "cliVersion": "4.22.0",
+ "cliVersion": "5.35.2",
"generatorName": "fernapi/fern-python-sdk",
"generatorVersion": "4.63.4",
"generatorConfig": {
@@ -9,6 +9,6 @@
"use_str_enums": false
}
},
- "originGitCommit": "3ad89f5558f1774e8535f034a87bfe8c2fdfcd50",
- "sdkVersion": "2.0.0"
+ "originGitCommit": "005f29a7da515da7f49554b529cc7ace5b98128c",
+ "sdkVersion": "2.0.1"
}
\ No newline at end of file
diff --git a/.fern/replay.lock b/.fern/replay.lock
new file mode 100644
index 00000000..ef809498
--- /dev/null
+++ b/.fern/replay.lock
@@ -0,0 +1,10 @@
+# DO NOT EDIT MANUALLY - Managed by Fern Replay
+version: "1.0"
+generations:
+ - commit_sha: 1e6e7e02a6c6be33f48b6569f6ca20dc332a6c1c
+ tree_hash: 5fb8f4ca091f71069b3a2604ea9f5211f2e417e5
+ timestamp: 2026-10-15T18:34:30.958Z
+ cli_version: unknown
+ generator_versions: {}
+current_generation: 1e6e7e02a6c6be33f48b6569f6ca20dc332a6c1c
+patches: []
diff --git a/.fernignore b/.fernignore
index e83159bd..e2a45657 100644
--- a/.fernignore
+++ b/.fernignore
@@ -4,4 +4,7 @@ README.md
assets/
src/webflow/oauth.py
-src/webflow/types/oauth_scope.py
\ No newline at end of file
+src/webflow/types/oauth_scope.py
+.fern/replay.lock
+.fern/replay.yml
+.gitattributes
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..74928d6a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+.fern/replay.lock linguist-generated=true
diff --git a/poetry.lock b/poetry.lock
index 83fdc8b0..6a54488c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -38,13 +38,13 @@ trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
-version = "2026.2.25"
+version = "2026.7.22"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
files = [
- {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"},
- {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"},
+ {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"},
+ {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"},
]
[[package]]
@@ -147,17 +147,17 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "idna"
-version = "3.11"
+version = "3.15"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.8"
files = [
- {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
- {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
+ {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"},
+ {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"},
]
[package.extras]
-all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "iniconfig"
@@ -236,13 +236,13 @@ files = [
[[package]]
name = "packaging"
-version = "26.0"
+version = "26.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"},
- {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"},
+ {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
+ {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
]
[[package]]
@@ -517,58 +517,76 @@ files = [
[[package]]
name = "tomli"
-version = "2.4.0"
+version = "2.5.0"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
files = [
- {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"},
- {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"},
- {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"},
- {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"},
- {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"},
- {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"},
- {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"},
- {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"},
- {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"},
- {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"},
- {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"},
- {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"},
- {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"},
- {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"},
- {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"},
- {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"},
- {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"},
- {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"},
- {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"},
- {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"},
- {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"},
- {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"},
- {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"},
- {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"},
- {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"},
- {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"},
- {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"},
- {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"},
- {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"},
- {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"},
- {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"},
- {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"},
- {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"},
- {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"},
- {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"},
- {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"},
- {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"},
- {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"},
- {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"},
- {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"},
- {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"},
- {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"},
- {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"},
- {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"},
- {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"},
- {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"},
- {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"},
+ {file = "tomli-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545"},
+ {file = "tomli-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef"},
+ {file = "tomli-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b"},
+ {file = "tomli-2.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56"},
+ {file = "tomli-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1"},
+ {file = "tomli-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885"},
+ {file = "tomli-2.5.0-cp311-cp311-win32.whl", hash = "sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e"},
+ {file = "tomli-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8"},
+ {file = "tomli-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980"},
+ {file = "tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df"},
+ {file = "tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b"},
+ {file = "tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0"},
+ {file = "tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6"},
+ {file = "tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc"},
+ {file = "tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7"},
+ {file = "tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2"},
+ {file = "tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7"},
+ {file = "tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea"},
+ {file = "tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea"},
+ {file = "tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043"},
+ {file = "tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0"},
+ {file = "tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b"},
+ {file = "tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066"},
+ {file = "tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b"},
+ {file = "tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68"},
+ {file = "tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc"},
+ {file = "tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84"},
+ {file = "tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105"},
+ {file = "tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646"},
+ {file = "tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b"},
+ {file = "tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75"},
+ {file = "tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb"},
+ {file = "tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3"},
+ {file = "tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b"},
+ {file = "tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a"},
+ {file = "tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3"},
+ {file = "tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4"},
+ {file = "tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d"},
+ {file = "tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9"},
+ {file = "tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f"},
+ {file = "tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374"},
+ {file = "tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442"},
+ {file = "tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03"},
+ {file = "tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1"},
+ {file = "tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0"},
+ {file = "tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc"},
+ {file = "tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276"},
+ {file = "tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52"},
+ {file = "tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7"},
+ {file = "tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391"},
+ {file = "tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859"},
+ {file = "tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb"},
+ {file = "tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5"},
+ {file = "tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd"},
+ {file = "tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57"},
+ {file = "tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd"},
+ {file = "tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01"},
+ {file = "tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f"},
+ {file = "tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a"},
+ {file = "tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142"},
+ {file = "tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5"},
+ {file = "tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571"},
+ {file = "tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7"},
+ {file = "tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b"},
+ {file = "tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6"},
]
[[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 786103ad..2aa101e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ dynamic = ["version"]
[tool.poetry]
name = "webflow"
-version = "2.0.0"
+version = "2.0.1"
description = ""
readme = "README.md"
authors = []
diff --git a/reference.md b/reference.md
index fbfbd16e..814c3c88 100644
--- a/reference.md
+++ b/reference.md
@@ -79,7 +79,9 @@ client.token.authorized_by()
Information about the authorization token
-Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+Access to this endpoint requires a bearer token from either a [Data Client App](/data/docs/data-clients/getting-started), a [Workspace token](/data/reference/authentication/workspace-token), or a [Site token](/data/reference/authentication/site-token).
+
+The `application` object is only returned for tokens issued to an OAuth app. For tokens without an OAuth app, such as Site and Workspace API tokens (`grantType: client_credentials`), the response omits `application`.
@@ -126,6 +128,76 @@ client.token.introspect()
+
+
+
+
+## Workspaces
+client.workspaces.list() -> Workspaces
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List the workspaces the token can access, including each workspace's ID. Use this endpoint to find the `workspace_id` that other workspace endpoints require.
+
+The response includes the workspaces granted to the token, plus the workspace that owns each site granted to the token. It returns only active workspaces (not archived, deleted, or draft) and isn't paginated.
+
+This endpoint supports workspace API tokens and Data Client apps.
+
+Required scope | `workspace:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.workspaces.list()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
@@ -189,7 +261,7 @@ client.sites.create(
-
-**workspace_id:** `str` — Unique identifier for a Workspace
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -630,10 +702,15 @@ client.sites.get_custom_domain(
-
-Publishes a site to one or more more domains.
+Publishes a site or an individual page to one or more domains.
+If multiple individual pages are published to staging, publishing from staging to production publishes all staged changes.
To publish to a specific custom domain, use the domain IDs from the [Get Custom Domains](/data/reference/sites/get-custom-domain) endpoint.
+You must include at least one of the `customDomains` or `publishToWebflowSubdomain` properties in the request body.
+
+To publish an individual page instead of the entire site, provide the ID of the page in the `pageId` parameter.
+
This endpoint has a specific rate limit of one successful publish queue per minute.
Required scope | `sites:write`
@@ -706,6 +783,88 @@ client.sites.publish(
-
+**page_id:** `typing.Optional[str]` — The ID of the page to publish
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Optimize
+client.optimize.list() -> OptimizeReferenceListResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Lists the `optimizeId` values the provided access token can access, and the resource each one comes from, such as a Webflow site or a non-Webflow site. Use an `optimizeId` with the other Optimize endpoints.
+
+Results are sorted by `optimizeId` and aren't paginated.
+
+Webflow sites accept OAuth app tokens and Site API tokens. Non-Webflow sites that use Optimize accept only OAuth app tokens, including tokens authorized for a whole workspace.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.list()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1072,6 +1231,119 @@ client.collections.delete(
+
+
+
+
+client.collections.patch(...) -> Collection
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Update a collection's display name, singular name, slug, or field groups.
+
+**Field group rules:**
+- A collection can have a maximum of 50 field groups
+- Each `displayName` must be unique across all field groups in the collection
+- Each `fieldId` must be unique across all field groups in the collection
+- Ecommerce collections do not support field groups
+
+Required scope | `cms:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.patch(
+ collection_id="580e63fc8c9a982ac9b8b745",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**display_name:** `typing.Optional[str]` — Name given to the Collection
+
+
+
+
+
+-
+
+**singular_name:** `typing.Optional[str]` — The name of one Item in Collection (e.g. ”Blog Post” if the Collection is called “Blog Posts”)
+
+
+
+
+
+-
+
+**slug:** `typing.Optional[str]` — Slug of Collection in Site URL structure
+
+
+
+
+
+-
+
+**field_groups:** `typing.Optional[typing.List[FieldGroup]]` — The list of field groups in the Collection. Replaces the existing field groups.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
@@ -1147,7 +1419,7 @@ client.pages.list(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -1223,6 +1495,7 @@ client = Webflow(
client.pages.get_metadata(
page_id="63c720f9347c2139b248e552",
locale_id="65427cf400e02b306eaa04a0",
+ translatable="65427cf400e02b306eaa04a0",
)
```
@@ -1251,7 +1524,27 @@ client.pages.get_metadata(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -1352,7 +1645,7 @@ client.pages.update_page_settings(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -1372,8 +1665,9 @@ Unique identifier for a specific Locale.
Slug for the page.
-
-**Note:** Updating slugs in secondary locales is only supported in Advanced and Enterprise localization add-on plans.
+**Note:** The slug field is ignored in the following cases — all other fields in the same request still apply:
+- The site's home page, collection template pages, and utility pages (e.g. 404, password, search).
+- For secondary locales, updating the slug requires an Advanced or Enterprise localization add-on plan.
@@ -1453,6 +1747,7 @@ client.pages.get_content(
locale_id="65427cf400e02b306eaa04a0",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
```
@@ -1481,7 +1776,7 @@ client.pages.get_content(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -1505,8 +1800,28 @@ Unique identifier for a specific Locale.
-
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
-
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
@@ -1790,6 +2105,7 @@ client.components.get_content(
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
```
@@ -1826,7 +2142,7 @@ client.components.get_content(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -1858,6 +2174,26 @@ Unique identifier for a specific Locale.
-
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -1987,7 +2323,7 @@ client.components.update_content(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -2061,6 +2397,7 @@ client.components.get_properties(
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
```
@@ -2097,7 +2434,7 @@ client.components.get_properties(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -2129,6 +2466,26 @@ Unique identifier for a specific Locale.
-
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2244,7 +2601,7 @@ client.components.update_properties(
Unique identifier for a specific Locale.
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -2291,6 +2648,8 @@ Get a list of scripts that have been registered to a site. A site can have a max
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
@@ -2370,6 +2729,8 @@ Register a hosted script to a site.
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
@@ -2493,6 +2854,8 @@ Register an inline script to a site. Inline scripts are limited to 2000 characte
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
@@ -2637,8 +3000,10 @@ client = Webflow(
client.assets.list(
site_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
offset=1,
limit=1,
+ folder_id="folderId",
)
```
@@ -2663,6 +3028,18 @@ client.assets.list(
-
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
@@ -2679,6 +3056,17 @@ client.assets.list(
-
+**folder_id:** `typing.Optional[str]`
+
+Filter assets to those in the specified folder and all descendant folders.
+Must be a 24-character hex ObjectId.
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2840,6 +3228,7 @@ client = Webflow(
client.assets.get(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
```
@@ -2864,6 +3253,18 @@ client.assets.get(
-
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -2990,6 +3391,7 @@ client = Webflow(
client.assets.update(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
```
@@ -3014,7 +3416,19 @@ client.assets.update(
-
-**locale_id:** `typing.Optional[str]` — Unique identifier for a specific locale. Applicable, when using localization.
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**display_name:** `typing.Optional[str]` — A human readable name for the asset. This value is not localizable.
@@ -3022,7 +3436,7 @@ client.assets.update(
-
-**display_name:** `typing.Optional[str]` — A human readable name for the asset
+**alt_text:** `typing.Optional[str]` — Alternate text describing the image
@@ -3284,8 +3698,8 @@ client.assets.get_folder(
-## Webhooks
-client.webhooks.list(...) -> WebhookList
+## Custom Fonts
+client.custom_fonts.list(...) -> CustomFonts
-
@@ -3297,7 +3711,7 @@ client.assets.get_folder(
-
-List all App-created Webhooks registered for a given site
+List the custom fonts uploaded to a site.
Required scope | `sites:read`
@@ -3322,8 +3736,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.webhooks.list(
+client.custom_fonts.list(
site_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
)
```
@@ -3348,6 +3764,22 @@ client.webhooks.list(
-
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -3360,7 +3792,7 @@ client.webhooks.list(
-client.webhooks.create(...) -> Webhook
+client.custom_fonts.create(...) -> CustomFontCreateResponse
-
@@ -3372,11 +3804,14 @@ client.webhooks.list(
-
-Create a new Webhook.
+Register a custom font on a site and get a presigned S3 URL to upload the font binary.
-Limit of 75 registrations per `triggerType`, per site.
+The response includes a `customFont` object and an `upload` object. Use the `upload.url` and `upload.fields`
+to POST the font binary directly to S3 as `multipart/form-data`. The binary must go in a field named `file`
+and must be the last field in the form (an AWS S3 requirement). S3 returns `201 Created` on a successful upload.
+
+To learn more, see [Custom fonts](/data/docs/custom-fonts).
-Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
Required scope | `sites:write`
@@ -3394,22 +3829,20 @@ Required scope | `sites:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
-import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.webhooks.create(
- site_id_="580e63e98c9a982ac9b8b741",
- id="582266e0cd48de0f0e3c6d8b",
- trigger_type="form_submission",
- url="https://webhook.site/7f7f7f7f-7f7f-7f7f-7f7f-7f7f7f7f7f7f",
- workspace_id="4f4e46fd476ea8c507000001",
- site_id="562ac0395358780a1f5e6fbd",
- last_triggered=datetime.datetime.fromisoformat("2023-02-08T23:59:28+00:00"),
- created_on=datetime.datetime.fromisoformat("2022-11-08T23:59:28+00:00"),
+client.custom_fonts.create(
+ site_id="580e63e98c9a982ac9b8b741",
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
)
```
@@ -3434,7 +3867,55 @@ client.webhooks.create(
-
-**request:** `Webhook`
+**file_name:** `str` — File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+
+
+
+
+-
+
+**file_hash:** `str` — Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+
+
+
+
+-
+
+**font_family:** `str` — The CSS font-family name (1-256 characters). Commas are stripped server-side.
+
+
+
+
+
+-
+
+**weight:** `int` — CSS font-weight value (1-1000)
+
+
+
+
+
+-
+
+**italic:** `bool` — Whether the font is italic
+
+
+
+
+
+-
+
+**font_display:** `CustomFontsCreateRequestFontDisplay` — CSS font-display value
+
+
+
+
+
+-
+
+**axes:** `typing.Optional[typing.List[CustomFontAxis]]` — Variable font axes. Omit or pass an empty array for static fonts.
@@ -3454,7 +3935,7 @@ client.webhooks.create(
-client.webhooks.get(...) -> Webhook
+client.custom_fonts.get(...) -> CustomFontsGetResponse
-
@@ -3466,9 +3947,9 @@ client.webhooks.create(
-
-Get a specific Webhook instance
+Get details about a custom font on a site.
-Required scope: `sites:read`
+Required scope | `sites:read`
@@ -3491,8 +3972,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.webhooks.get(
- webhook_id="580e64008c9a982ac9b8b754",
+client.custom_fonts.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
)
```
@@ -3509,7 +3991,15 @@ client.webhooks.get(
-
-**webhook_id:** `str` — Unique identifier for a Webhook
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**font_id:** `str` — Unique identifier for a custom font on a site
@@ -3529,7 +4019,7 @@ client.webhooks.get(
-client.webhooks.delete(...)
+client.custom_fonts.delete(...)
-
@@ -3541,9 +4031,9 @@ client.webhooks.get(
-
-Remove a Webhook
+Delete a custom font from a site.
-Required scope: `sites:read`
+Required scope | `sites:write`
@@ -3566,8 +4056,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.webhooks.delete(
- webhook_id="580e64008c9a982ac9b8b754",
+client.custom_fonts.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
)
```
@@ -3584,7 +4075,15 @@ client.webhooks.delete(
-
-**webhook_id:** `str` — Unique identifier for a Webhook
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**font_id:** `str` — Unique identifier for a custom font on a site
@@ -3604,8 +4103,7 @@ client.webhooks.delete(
-## Forms
-client.forms.list(...) -> FormList
+client.custom_fonts.update(...) -> CustomFontsUpdateResponse
-
@@ -3617,9 +4115,12 @@ client.webhooks.delete(
-
-List forms for a given site.
+Update the metadata of a custom font. The font binary is not changed by this endpoint.
+To replace the binary, use [Replace custom font file](#operation/replace-custom-font-file).
-Required scope | `forms:read`
+The request body must include at least one of `fontFamily`, `weight`, `italic`, or `fontDisplay`.
+
+Required scope | `sites:write`
@@ -3642,10 +4143,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.forms.list(
+client.custom_fonts.update(
site_id="580e63e98c9a982ac9b8b741",
- limit=1,
- offset=1,
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
)
```
@@ -3670,7 +4170,7 @@ client.forms.list(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**font_id:** `str` — Unique identifier for a custom font on a site
@@ -3678,7 +4178,31 @@ client.forms.list(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**font_family:** `typing.Optional[str]` — The CSS font-family name (1-256 characters)
+
+
+
+
+
+-
+
+**weight:** `typing.Optional[int]` — CSS font-weight value (1-1000)
+
+
+
+
+
+-
+
+**italic:** `typing.Optional[bool]` — Whether the font is italic
+
+
+
+
+
+-
+
+**font_display:** `typing.Optional[CustomFontsUpdateRequestFontDisplay]` — CSS font-display value
@@ -3698,7 +4222,7 @@ client.forms.list(
-client.forms.get(...) -> Form
+client.custom_fonts.replace_file(...) -> CustomFontCreateResponse
-
@@ -3710,9 +4234,14 @@ client.forms.list(
-
-Get information about a given form.
+Replace the binary of an existing custom font while preserving its ID and any references to it.
+The upload handshake is identical to [Create custom font](#operation/create-custom-font).
-Required scope | `forms:read`
+If the existing font has a non-empty `axes` array (a variable font), you must include an `axes` field
+in the request. Send `axes: []` to declare that the new binary is a static font, or send the new variable
+axes to declare it is still variable. Omitting `axes` when the existing font is variable returns `400`.
+
+Required scope | `sites:write`
@@ -3735,8 +4264,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.forms.get(
- form_id="580e63e98c9a982ac9b8b741",
+client.custom_fonts.replace_file(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ file_name="AcmeSans-Regular-v2.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
)
```
@@ -3753,7 +4285,7 @@ client.forms.get(
-
-**form_id:** `str` — Unique identifier for a Form
+**site_id:** `str` — Unique identifier for a Site
@@ -3761,39 +4293,79 @@ client.forms.get(
-
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+**font_id:** `str` — Unique identifier for a custom font on a site
-
-
+
+-
+**file_name:** `str` — File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
-
-client.forms.list_submissions(...) -> FormSubmissionList
-
-#### 📝 Description
+**file_hash:** `str` — Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+
+
-
+**axes:** `typing.Optional[typing.List[CustomFontAxis]]` — Variable font axes for the replacement binary. Required when the existing font has a non-empty `axes` array.
+
+
+
+
-
-List form submissions for a given form
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
-
- When a form is used in a component definition, each instance of the form is considered a unique form.
- To get a combined list of submissions for a form that appears across multiple component instances, use the [List Form Submissions by Site](/data/reference/forms/form-submissions/list-submissions-by-site) endpoint.
-
+
+
+
-Required scope | `forms:read`
+client.custom_fonts.batch_create(...) -> CustomFontBatchCreateResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Register 1–25 custom fonts in a single request and get a presigned S3 URL for each one.
+This collapses the registration step for a whole font family (for example, Regular, Bold,
+Italic, and Bold Italic) into one rate-limited request.
+
+Registration is batched, but the binary uploads are not: the response contains one `upload`
+object per registered font, and you must POST each font binary to its own presigned S3 URL
+exactly as you would for [Create custom font](#operation/create-custom-font). The Webflow API
+server never receives the raw font bytes.
+
+The response is `200 OK` for a valid request body. Per-font results are reported in the
+`created` and `failed` arrays. If the site's font limit is reached partway through the batch,
+the fonts that still fit are registered and returned in `created`, while the rest appear in
+`failed` with `name: "FontLimitReached"` — valid fonts are never discarded because a later
+font in the same batch could not be registered. Each presigned URL expires approximately
+15 minutes after issuance, so upload the binaries promptly.
+
+Required scope | `sites:write`
@@ -3810,16 +4382,25 @@ Required scope | `forms:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+from webflow.custom_fonts import CustomFontBatchCreateRequestItemsItem
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.forms.list_submissions(
- form_id="580e63e98c9a982ac9b8b741",
- offset=1,
- limit=1,
+client.custom_fonts.batch_create(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchCreateRequestItemsItem(
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
+ )
+ ],
)
```
@@ -3836,15 +4417,7 @@ client.forms.list_submissions(
-
-**form_id:** `str` — Unique identifier for a Form
-
-
-
-
-
--
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**site_id:** `str` — Unique identifier for a Site
@@ -3852,7 +4425,7 @@ client.forms.list_submissions(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**items:** `typing.List[CustomFontBatchCreateRequestItemsItem]` — The custom fonts to register. Each item uses the same shape as the single-font create request.
@@ -3872,7 +4445,7 @@ client.forms.list_submissions(
-client.forms.get_submission(...) -> FormSubmission
+client.custom_fonts.batch_delete(...) -> CustomFontBatchDeleteResponse
-
@@ -3884,9 +4457,14 @@ client.forms.list_submissions(
-
-Get information about a given form submissio.
+Delete 1-100 custom fonts in a single request. The response is always `200 OK` for a valid request body.
+Per-font results are reported in the `deleted` and `failed` arrays.
-Required scope | `forms:read`
+The endpoint is idempotent: fonts that do not exist appear in `failed` with `name: "NotFound"` rather than
+failing the entire request. You can safely retry a partial failure by re-sending only the IDs that did not
+appear in `deleted`.
+
+Required scope | `sites:write`
@@ -3903,14 +4481,20 @@ Required scope | `forms:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+from webflow.custom_fonts import CustomFontBatchDeleteRequestItemsItem
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.forms.get_submission(
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.custom_fonts.batch_delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchDeleteRequestItemsItem(
+ id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ ],
)
```
@@ -3927,7 +4511,15 @@ client.forms.get_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**items:** `typing.List[CustomFontBatchDeleteRequestItemsItem]`
@@ -3947,7 +4539,8 @@ client.forms.get_submission(
-client.forms.delete_submission(...)
+## Webhooks
+client.webhooks.list(...) -> WebhookList
-
@@ -3959,10 +4552,9 @@ client.forms.get_submission(
-
-Delete a form submission
-
+List all App-created Webhooks registered for a given site
-Required scope | `forms:write`
+Required scope | `sites:read`
@@ -3985,8 +4577,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.forms.delete_submission(
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.webhooks.list(
+ site_id="580e63e98c9a982ac9b8b741",
)
```
@@ -4003,7 +4595,7 @@ client.forms.delete_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**site_id:** `str` — Unique identifier for a Site
@@ -4023,7 +4615,7 @@ client.forms.delete_submission(
-client.forms.update_submission(...) -> FormSubmission
+client.webhooks.create(...) -> Webhook
-
@@ -4035,9 +4627,12 @@ client.forms.delete_submission(
-
-Update hidden fields on a form submission
+Create a new Webhook.
-Required scope | `forms:write`
+Limit of 75 registrations per `triggerType`, per site.
+
+Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/data-clients/getting-started).
+Required scope | `sites:write`
@@ -4054,14 +4649,22 @@ Required scope | `forms:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.forms.update_submission(
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.webhooks.create(
+ site_id_="580e63e98c9a982ac9b8b741",
+ id="582266e0cd48de0f0e3c6d8b",
+ trigger_type="form_submission",
+ url="https://webhook.site/7f7f7f7f-7f7f-7f7f-7f7f-7f7f7f7f7f7f",
+ workspace_id="4f4e46fd476ea8c507000001",
+ site_id="562ac0395358780a1f5e6fbd",
+ last_triggered=datetime.datetime.fromisoformat("2023-02-08T23:59:28+00:00"),
+ created_on=datetime.datetime.fromisoformat("2022-11-08T23:59:28+00:00"),
)
```
@@ -4078,7 +4681,7 @@ client.forms.update_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**site_id:** `str` — Unique identifier for a Site
@@ -4086,7 +4689,7 @@ client.forms.update_submission(
-
-**form_submission_data:** `typing.Optional[typing.Dict[str, typing.Any]]` — An existing **hidden field** defined on the form schema, and the corresponding value to set
+**request:** `Webhook`
@@ -4106,8 +4709,7 @@ client.forms.update_submission(
-## Products
-client.products.list(...) -> ProductAndSkUsList
+client.webhooks.get(...) -> Webhook
-
@@ -4119,12 +4721,9 @@ client.forms.update_submission(
-
-Retrieve all products for a site.
-
-Use `limit` and `offset` to page through all products with subsequent requests. All SKUs for each product
-will also be fetched and returned. The `limit`, `offset` and `total` values represent Products only and do not include any SKUs.
+Get a specific Webhook instance
-Required scope | `ecommerce:read`
+Required scope: `sites:read`
@@ -4147,10 +4746,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.products.list(
- site_id="580e63e98c9a982ac9b8b741",
- offset=1,
- limit=1,
+client.webhooks.get(
+ webhook_id="580e64008c9a982ac9b8b754",
)
```
@@ -4167,23 +4764,7 @@ client.products.list(
-
-**site_id:** `str` — Unique identifier for a Site
-
-
-
-
-
--
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
-
-
-
-
-
--
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**webhook_id:** `str` — Unique identifier for a Webhook
@@ -4203,7 +4784,7 @@ client.products.list(
-client.products.create(...) -> ProductAndSkUs
+client.webhooks.delete(...)
-
@@ -4215,16 +4796,9 @@ client.products.list(
-
-Create a new ecommerce product and defaultSKU. A product, at minimum, must have a single SKU.
-
-To create a product with multiple SKUs:
- - First, create a list of `sku-properties`, also known as [product options](https://help.webflow.com/hc/en-us/articles/33961334531347-Create-product-options-and-variants). For example, a T-shirt product may have a "color" `sku-property`, with a list of enum values: red, yellow, and blue, another `sku-property` may be "size", with a list of enum values: small, medium, and large.
- - Once, a product is created with a list of `sku-properties`, Webflow will create a **default SKU**, which is always a combination of the first `enum` values of each `sku-property`. (e.g. Small - Red - T-Shirt)
- - After creation, you can create additional SKUs for the product, using the [Create SKUs endpoint.](/data/reference/ecommerce/products/create-sku)
-
-Upon creation, the default product type will be `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
+Remove a Webhook
-Required scope | `ecommerce:write`
+Required scope: `sites:read`
@@ -4239,81 +4813,16 @@ Required scope | `ecommerce:write`
-
```python
-from webflow import Webflow, ProductFieldData, SkuPropertyList, SkuPropertyListEnumItem, SkuFieldData, SkuFieldDataPrice
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
-from webflow.products import ProductSkuCreateProduct, ProductSkuCreateSku
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.products.create(
- site_id="580e63e98c9a982ac9b8b741",
- publish_status="staging",
- product=ProductSkuCreateProduct(
- field_data=ProductFieldData(
- name="Colorful T-shirt",
- slug="colorful-t-shirt",
- description="Our best-selling t-shirt available in multiple colors and sizes",
- sku_properties=[
- SkuPropertyList(
- id="color",
- name="Color",
- enum=[
- SkuPropertyListEnumItem(
- id="red",
- name="Red",
- slug="red",
- ),
- SkuPropertyListEnumItem(
- id="yellow",
- name="Yellow",
- slug="yellow",
- ),
- SkuPropertyListEnumItem(
- id="blue",
- name="Blue",
- slug="blue",
- )
- ],
- ),
- SkuPropertyList(
- id="size",
- name="Size",
- enum=[
- SkuPropertyListEnumItem(
- id="small",
- name="Small",
- slug="small",
- ),
- SkuPropertyListEnumItem(
- id="medium",
- name="Medium",
- slug="medium",
- ),
- SkuPropertyListEnumItem(
- id="large",
- name="Large",
- slug="large",
- )
- ],
- )
- ],
- ),
- ),
- sku=ProductSkuCreateSku(
- field_data=SkuFieldData(
- name="Colorful T-shirt - Red Small",
- slug="colorful-t-shirt-red-small",
- price=SkuFieldDataPrice(
- value=2499,
- unit="USD",
- currency="USD",
- ),
- main_image="https://rocketamp-sample-store.myshopify.com/cdn/shop/products/Gildan_2000_Antique_Cherry_Red_Front_1024x1024.jpg?v=1527232987",
- ),
- ),
+client.webhooks.delete(
+ webhook_id="580e64008c9a982ac9b8b754",
)
```
@@ -4330,7 +4839,7 @@ client.products.create(
-
-**site_id:** `str` — Unique identifier for a Site
+**webhook_id:** `str` — Unique identifier for a Webhook
@@ -4338,58 +4847,34 @@ client.products.create(
-
-**product:** `ProductSkuCreateProduct`
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
-
--
-**sku:** `ProductSkuCreateSku`
-
+
+## Forms
+client.forms.list(...) -> FormList
-
-**publish_status:** `typing.Optional[PublishStatus]`
-
-
-
+#### 📝 Description
-
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
-
-
-
-
-
-
-
-
-
-
-
-client.products.get(...) -> ProductAndSkUs
-
--
-
-#### 📝 Description
-
-
-
--
-
-Retrieve a single product by its ID. All of its SKUs will also be
-retrieved.
+List forms for a given site.
-Required scope | `ecommerce:read`
+Required scope | `forms:read`
@@ -4412,9 +4897,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.products.get(
+client.forms.list(
site_id="580e63e98c9a982ac9b8b741",
- product_id="580e63fc8c9a982ac9b8b745",
+ limit=1,
+ offset=1,
)
```
@@ -4439,7 +4925,15 @@ client.products.get(
-
-**product_id:** `str` — Unique identifier for a Product
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
@@ -4459,7 +4953,7 @@ client.products.get(
-client.products.update(...) -> Product
+client.forms.get(...) -> Form
-
@@ -4471,11 +4965,9 @@ client.products.get(
-
-Update an existing Product.
-
-Updating an existing Product will set the product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
+Get information about a given form.
-Required scope | `ecommerce:write`
+Required scope | `forms:read`
@@ -4498,9 +4990,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.products.update(
- site_id="580e63e98c9a982ac9b8b741",
- product_id="580e63fc8c9a982ac9b8b745",
+client.forms.get(
+ form_id="580e63e98c9a982ac9b8b741",
)
```
@@ -4517,39 +5008,7 @@ client.products.update(
-
-**site_id:** `str` — Unique identifier for a Site
-
-
-
-
-
--
-
-**product_id:** `str` — Unique identifier for a Product
-
-
-
-
-
--
-
-**publish_status:** `typing.Optional[PublishStatus]`
-
-
-
-
-
--
-
-**product:** `typing.Optional[Product]`
-
-
-
-
-
--
-
-**sku:** `typing.Optional[Sku]`
+**form_id:** `str` — Unique identifier for a Form
@@ -4569,7 +5028,7 @@ client.products.update(
-client.products.create_sku(...) -> ProductsCreateSkuResponse
+client.forms.list_submissions(...) -> FormSubmissionList
-
@@ -4581,11 +5040,15 @@ client.products.update(
-
-Create additional SKUs to manage every [option and variant of your Product.](https://help.webflow.com/hc/en-us/articles/33961334531347-Create-product-options-and-variants)
+List form submissions for a given form
-Creating SKUs through the API will set the product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
+
+ When a form is used in a component definition, each instance of the form is considered a unique form.
-Required scope | `ecommerce:write`
+ To get a combined list of submissions for a form that appears across multiple component instances, use the [List Form Submissions by Site](/data/reference/forms/form-submissions/list-submissions-by-site) endpoint.
+
+
+Required scope | `forms:read`
@@ -4600,36 +5063,18 @@ Required scope | `ecommerce:write`
-
```python
-from webflow import Webflow, Sku, SkuFieldData, SkuFieldDataPrice
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
-import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.products.create_sku(
- site_id="580e63e98c9a982ac9b8b741",
- product_id="580e63fc8c9a982ac9b8b745",
- skus=[
- Sku(
- id="66072fb71b89448912e2681c",
- cms_locale_id="653ad57de882f528b32e810e",
- last_published=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- last_updated=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- created_on=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- field_data=SkuFieldData(
- name="Colorful T-shirt - Default",
- slug="colorful-t-shirt-default",
- price=SkuFieldDataPrice(
- value=2499,
- unit="USD",
- currency="USD",
- ),
- ),
- )
- ],
+client.forms.list_submissions(
+ form_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
)
```
@@ -4646,15 +5091,7 @@ client.products.create_sku(
-
-**site_id:** `str` — Unique identifier for a Site
-
-
-
-
-
--
-
-**product_id:** `str` — Unique identifier for a Product
+**form_id:** `str` — Unique identifier for a Form
@@ -4662,7 +5099,7 @@ client.products.create_sku(
-
-**skus:** `typing.List[Sku]` — An array of the SKU data your are adding
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
@@ -4670,7 +5107,7 @@ client.products.create_sku(
-
-**publish_status:** `typing.Optional[PublishStatus]`
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -4690,7 +5127,7 @@ client.products.create_sku(
-client.products.update_sku(...) -> Sku
+client.forms.get_submission(...) -> FormSubmission
-
@@ -4702,11 +5139,9 @@ client.products.create_sku(
-
-Update a specified SKU.
-
-Updating an existing SKU will set the Product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
+Get information about a given form submissio.
-Required scope | `ecommerce:write`
+Required scope | `forms:read`
@@ -4721,35 +5156,16 @@ Required scope | `ecommerce:write`
-
```python
-from webflow import Webflow, Sku, SkuFieldData, SkuFieldDataPrice
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
-import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.products.update_sku(
- site_id="580e63e98c9a982ac9b8b741",
- product_id="580e63fc8c9a982ac9b8b745",
- sku_id="5e8518516e147040726cc415",
- sku=Sku(
- id="66072fb71b89448912e2681c",
- cms_locale_id="653ad57de882f528b32e810e",
- last_published=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- last_updated=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- created_on=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
- field_data=SkuFieldData(
- name="Colorful T-shirt - Default",
- slug="colorful-t-shirt-default",
- price=SkuFieldDataPrice(
- value=2499,
- unit="USD",
- currency="USD",
- ),
- ),
- ),
+client.forms.get_submission(
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -4766,39 +5182,7 @@ client.products.update_sku(
-
-**site_id:** `str` — Unique identifier for a Site
-
-
-
-
-
--
-
-**product_id:** `str` — Unique identifier for a Product
-
-
-
-
-
--
-
-**sku_id:** `str` — Unique identifier for a SKU
-
-
-
-
-
--
-
-**sku:** `Sku`
-
-
-
-
-
--
-
-**publish_status:** `typing.Optional[PublishStatus]`
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -4818,8 +5202,7 @@ client.products.update_sku(
-## Orders
-client.orders.list(...) -> OrderList
+client.forms.delete_submission(...)
-
@@ -4831,9 +5214,10 @@ client.products.update_sku(
-
-List all orders created for a given site.
+Delete a form submission
-Required scope | `ecommerce:read`
+
+Required scope | `forms:write`
@@ -4856,11 +5240,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.orders.list(
- site_id="580e63e98c9a982ac9b8b741",
- status="pending",
- offset=1,
- limit=1,
+client.forms.delete_submission(
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -4877,31 +5258,7 @@ client.orders.list(
-
-**site_id:** `str` — Unique identifier for a Site
-
-
-
-
-
--
-
-**status:** `typing.Optional[OrdersListRequestStatus]` — Filter the orders by status
-
-
-
-
-
--
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
-
-
-
-
-
--
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -4921,7 +5278,7 @@ client.orders.list(
-client.orders.get(...) -> Order
+client.forms.update_submission(...) -> FormSubmission
-
@@ -4933,10 +5290,9 @@ client.orders.list(
-
-Retrieve a single product by its ID. All of its SKUs will also be
-retrieved.
+Update hidden fields on a form submission
-Required scope | `ecommerce:read`
+Required scope | `forms:write`
@@ -4959,9 +5315,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.orders.get(
- site_id="580e63e98c9a982ac9b8b741",
- order_id="5e8518516e147040726cc415",
+client.forms.update_submission(
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -4978,7 +5333,7 @@ client.orders.get(
-
-**site_id:** `str` — Unique identifier for a Site
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -4986,7 +5341,7 @@ client.orders.get(
-
-**order_id:** `str` — Unique identifier for an Order
+**form_submission_data:** `typing.Optional[typing.Dict[str, typing.Any]]` — An existing **hidden field** defined on the form schema, and the corresponding value to set
@@ -5006,7 +5361,8 @@ client.orders.get(
-client.orders.update(...) -> Order
+## Products
+client.products.list(...) -> ProductAndSkUsList
-
@@ -5018,11 +5374,12 @@ client.orders.get(
-
-This API lets you update the fields, `comment`, `shippingProvider`,
-and/or `shippingTracking` for a given order. All three fields can be
-updated simultaneously or independently.
+Retrieve all products for a site.
-Required scope | `ecommerce:write`
+Use `limit` and `offset` to page through all products with subsequent requests. All SKUs for each product
+will also be fetched and returned. The `limit`, `offset` and `total` values represent Products only and do not include any SKUs.
+
+Required scope | `ecommerce:read`
@@ -5045,9 +5402,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.orders.update(
+client.products.list(
site_id="580e63e98c9a982ac9b8b741",
- order_id="5e8518516e147040726cc415",
+ offset=1,
+ limit=1,
)
```
@@ -5072,31 +5430,7 @@ client.orders.update(
-
-**order_id:** `str` — Unique identifier for an Order
-
-
-
-
-
--
-
-**comment:** `typing.Optional[str]` — Arbitrary data for your records
-
-
-
-
-
--
-
-**shipping_provider:** `typing.Optional[str]` — Company or method used to ship order
-
-
-
-
-
--
-
-**shipping_tracking:** `typing.Optional[str]` — Tracking number for order shipment
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
@@ -5104,7 +5438,7 @@ client.orders.update(
-
-**shipping_tracking_url:** `typing.Optional[str]` — URL to track order shipment
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -5124,7 +5458,7 @@ client.orders.update(
-client.orders.update_fulfill(...) -> Order
+client.products.create(...) -> ProductAndSkUs
-
@@ -5136,7 +5470,14 @@ client.orders.update(
-
-Updates an order's status to fulfilled
+Create a new ecommerce product and defaultSKU. A product, at minimum, must have a single SKU.
+
+To create a product with multiple SKUs:
+ - First, create a list of `sku-properties`, also known as [product options](https://help.webflow.com/hc/en-us/articles/33961334531347-Create-product-options-and-variants). For example, a T-shirt product may have a "color" `sku-property`, with a list of enum values: red, yellow, and blue, another `sku-property` may be "size", with a list of enum values: small, medium, and large.
+ - Once, a product is created with a list of `sku-properties`, Webflow will create a **default SKU**, which is always a combination of the first `enum` values of each `sku-property`. (e.g. Small - Red - T-Shirt)
+ - After creation, you can create additional SKUs for the product, using the [Create SKUs endpoint.](/data/reference/ecommerce/products/create-sku)
+
+Upon creation, the default product type will be `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
Required scope | `ecommerce:write`
@@ -5153,17 +5494,81 @@ Required scope | `ecommerce:write`
-
```python
-from webflow import Webflow
+from webflow import Webflow, ProductFieldData, SkuPropertyList, SkuPropertyListEnumItem, SkuFieldData, SkuFieldDataPrice
from webflow.environment import WebflowEnvironment
+from webflow.products import ProductSkuCreateProduct, ProductSkuCreateSku
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.orders.update_fulfill(
+client.products.create(
site_id="580e63e98c9a982ac9b8b741",
- order_id="5e8518516e147040726cc415",
+ publish_status="staging",
+ product=ProductSkuCreateProduct(
+ field_data=ProductFieldData(
+ name="Colorful T-shirt",
+ slug="colorful-t-shirt",
+ description="Our best-selling t-shirt available in multiple colors and sizes",
+ sku_properties=[
+ SkuPropertyList(
+ id="color",
+ name="Color",
+ enum=[
+ SkuPropertyListEnumItem(
+ id="red",
+ name="Red",
+ slug="red",
+ ),
+ SkuPropertyListEnumItem(
+ id="yellow",
+ name="Yellow",
+ slug="yellow",
+ ),
+ SkuPropertyListEnumItem(
+ id="blue",
+ name="Blue",
+ slug="blue",
+ )
+ ],
+ ),
+ SkuPropertyList(
+ id="size",
+ name="Size",
+ enum=[
+ SkuPropertyListEnumItem(
+ id="small",
+ name="Small",
+ slug="small",
+ ),
+ SkuPropertyListEnumItem(
+ id="medium",
+ name="Medium",
+ slug="medium",
+ ),
+ SkuPropertyListEnumItem(
+ id="large",
+ name="Large",
+ slug="large",
+ )
+ ],
+ )
+ ],
+ ),
+ ),
+ sku=ProductSkuCreateSku(
+ field_data=SkuFieldData(
+ name="Colorful T-shirt - Red Small",
+ slug="colorful-t-shirt-red-small",
+ price=SkuFieldDataPrice(
+ value=2499,
+ unit="USD",
+ currency="USD",
+ ),
+ main_image="https://rocketamp-sample-store.myshopify.com/cdn/shop/products/Gildan_2000_Antique_Cherry_Red_Front_1024x1024.jpg?v=1527232987",
+ ),
+ ),
)
```
@@ -5188,7 +5593,7 @@ client.orders.update_fulfill(
-
-**order_id:** `str` — Unique identifier for an Order
+**product:** `ProductSkuCreateProduct`
@@ -5196,7 +5601,15 @@ client.orders.update_fulfill(
-
-**send_order_fulfilled_email:** `typing.Optional[bool]` — Whether or not the Order Fulfilled email should be sent
+**sku:** `ProductSkuCreateSku`
+
+
+
+
+
+-
+
+**publish_status:** `typing.Optional[PublishStatus]`
@@ -5216,7 +5629,7 @@ client.orders.update_fulfill(
-client.orders.update_unfulfill(...) -> Order
+client.products.get(...) -> ProductAndSkUs
-
@@ -5228,9 +5641,10 @@ client.orders.update_fulfill(
-
-Updates an order's status to unfulfilled
+Retrieve a single product by its ID. All of its SKUs will also be
+retrieved.
-Required scope | `ecommerce:write`
+Required scope | `ecommerce:read`
@@ -5253,9 +5667,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.orders.update_unfulfill(
+client.products.get(
site_id="580e63e98c9a982ac9b8b741",
- order_id="5e8518516e147040726cc415",
+ product_id="580e63fc8c9a982ac9b8b745",
)
```
@@ -5280,7 +5694,7 @@ client.orders.update_unfulfill(
-
-**order_id:** `str` — Unique identifier for an Order
+**product_id:** `str` — Unique identifier for a Product
@@ -5300,7 +5714,7 @@ client.orders.update_unfulfill(
-client.orders.refund(...) -> Order
+client.products.update(...) -> Product
-
@@ -5312,8 +5726,9 @@ client.orders.update_unfulfill(
-
-This API will reverse a Stripe charge and refund an order back to a
-customer. It will also set the order's status to `refunded`.
+Update an existing Product.
+
+Updating an existing Product will set the product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
Required scope | `ecommerce:write`
@@ -5338,9 +5753,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.orders.refund(
+client.products.update(
site_id="580e63e98c9a982ac9b8b741",
- order_id="5e8518516e147040726cc415",
+ product_id="580e63fc8c9a982ac9b8b745",
)
```
@@ -5365,7 +5780,7 @@ client.orders.refund(
-
-**order_id:** `str` — Unique identifier for an Order
+**product_id:** `str` — Unique identifier for a Product
@@ -5373,7 +5788,23 @@ client.orders.refund(
-
-**reason:** `typing.Optional[OrdersRefundRequestReason]` — The reason for the refund
+**publish_status:** `typing.Optional[PublishStatus]`
+
+
+
+
+
+-
+
+**product:** `typing.Optional[Product]`
+
+
+
+
+
+-
+
+**sku:** `typing.Optional[Sku]`
@@ -5393,8 +5824,7 @@ client.orders.refund(
-## Inventory
-client.inventory.list(...) -> InventoryItem
+client.products.create_sku(...) -> ProductsCreateSkuResponse
-
@@ -5406,9 +5836,11 @@ client.orders.refund(
-
-List the current inventory levels for a particular SKU item.
+Create additional SKUs to manage every [option and variant of your Product.](https://help.webflow.com/hc/en-us/articles/33961334531347-Create-product-options-and-variants)
-Required scope | `ecommerce:read`
+Creating SKUs through the API will set the product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
+
+Required scope | `ecommerce:write`
@@ -5423,17 +5855,36 @@ Required scope | `ecommerce:read`
-
```python
-from webflow import Webflow
+from webflow import Webflow, Sku, SkuFieldData, SkuFieldDataPrice
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.inventory.list(
- sku_collection_id="6377a7c4b7a79608c34a46f7",
- sku_id="5e8518516e147040726cc415",
+client.products.create_sku(
+ site_id="580e63e98c9a982ac9b8b741",
+ product_id="580e63fc8c9a982ac9b8b745",
+ skus=[
+ Sku(
+ id="66072fb71b89448912e2681c",
+ cms_locale_id="653ad57de882f528b32e810e",
+ last_published=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ last_updated=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ created_on=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ field_data=SkuFieldData(
+ name="Colorful T-shirt - Default",
+ slug="colorful-t-shirt-default",
+ price=SkuFieldDataPrice(
+ value=2499,
+ unit="USD",
+ currency="USD",
+ ),
+ ),
+ )
+ ],
)
```
@@ -5450,7 +5901,7 @@ client.inventory.list(
-
-**sku_collection_id:** `str` — Unique identifier for a SKU collection. Use the List Collections API to find this ID.
+**site_id:** `str` — Unique identifier for a Site
@@ -5458,7 +5909,23 @@ client.inventory.list(
-
-**sku_id:** `str` — Unique identifier for a SKU
+**product_id:** `str` — Unique identifier for a Product
+
+
+
+
+
+-
+
+**skus:** `typing.List[Sku]` — An array of the SKU data your are adding
+
+
+
+
+
+-
+
+**publish_status:** `typing.Optional[PublishStatus]`
@@ -5478,7 +5945,7 @@ client.inventory.list(
-client.inventory.update(...) -> InventoryItem
+client.products.update_sku(...) -> Sku
-
@@ -5490,11 +5957,9 @@ client.inventory.list(
-
-Updates the current inventory levels for a particular SKU item.
+Update a specified SKU.
-Updates may be given in one or two methods, absolutely or incrementally.
-- Absolute updates are done by setting `quantity` directly.
-- Incremental updates are by specifying the inventory delta in `updateQuantity` which is then added to the `quantity` stored on the server.
+Updating an existing SKU will set the Product type to `Advanced`, which ensures all Product and SKU fields will be shown to users in the Designer.
Required scope | `ecommerce:write`
@@ -5511,18 +5976,35 @@ Required scope | `ecommerce:write`
-
```python
-from webflow import Webflow
+from webflow import Webflow, Sku, SkuFieldData, SkuFieldDataPrice
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.inventory.update(
- sku_collection_id="6377a7c4b7a79608c34a46f7",
- sku_id="5e8518516e147040726cc415",
- inventory_type="infinite",
+client.products.update_sku(
+ site_id="580e63e98c9a982ac9b8b741",
+ product_id="580e63fc8c9a982ac9b8b745",
+ sku_id="5e8518516e147040726cc415",
+ sku=Sku(
+ id="66072fb71b89448912e2681c",
+ cms_locale_id="653ad57de882f528b32e810e",
+ last_published=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ last_updated=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ created_on=datetime.datetime.fromisoformat("2023-03-17T18:47:35+00:00"),
+ field_data=SkuFieldData(
+ name="Colorful T-shirt - Default",
+ slug="colorful-t-shirt-default",
+ price=SkuFieldDataPrice(
+ value=2499,
+ unit="USD",
+ currency="USD",
+ ),
+ ),
+ ),
)
```
@@ -5539,7 +6021,7 @@ client.inventory.update(
-
-**sku_collection_id:** `str` — Unique identifier for a SKU collection. Use the List Collections API to find this ID.
+**site_id:** `str` — Unique identifier for a Site
@@ -5547,7 +6029,7 @@ client.inventory.update(
-
-**sku_id:** `str` — Unique identifier for a SKU
+**product_id:** `str` — Unique identifier for a Product
@@ -5555,7 +6037,7 @@ client.inventory.update(
-
-**inventory_type:** `InventoryUpdateRequestInventoryType` — infinite or finite
+**sku_id:** `str` — Unique identifier for a SKU
@@ -5563,7 +6045,7 @@ client.inventory.update(
-
-**update_quantity:** `typing.Optional[float]` — Adds this quantity to currently store quantity. Can be negative.
+**sku:** `Sku`
@@ -5571,7 +6053,7 @@ client.inventory.update(
-
-**quantity:** `typing.Optional[float]` — Immediately sets quantity to this value.
+**publish_status:** `typing.Optional[PublishStatus]`
@@ -5591,8 +6073,8 @@ client.inventory.update(
-## Ecommerce
-client.ecommerce.get_settings(...) -> EcommerceSettings
+## Orders
+client.orders.list(...) -> OrderList
-
@@ -5604,7 +6086,7 @@ client.inventory.update(
-
-Retrieve ecommerce settings for a site.
+List all orders created for a given site.
Required scope | `ecommerce:read`
@@ -5629,8 +6111,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.ecommerce.get_settings(
+client.orders.list(
site_id="580e63e98c9a982ac9b8b741",
+ status="pending",
+ offset=1,
+ limit=1,
)
```
@@ -5655,6 +6140,30 @@ client.ecommerce.get_settings(
-
+**status:** `typing.Optional[OrdersListRequestStatus]` — Filter the orders by status
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -5667,8 +6176,7 @@ client.ecommerce.get_settings(
-## Collections Fields
-client.collections.fields.create(...) -> FieldCreate
+client.orders.get(...) -> Order
-
@@ -5680,13 +6188,10 @@ client.ecommerce.get_settings(
-
-Create a custom field in a collection.
-
-Field validation is currently not available through the API.
-
-Bulk creation of fields is not supported with this endpoint. To add multiple fields at once, include them when you [create the collection.](/data/v2.0.0/reference/cms/collections/create)
+Retrieve a single product by its ID. All of its SKUs will also be
+retrieved.
-Required scope | `cms:write`
+Required scope | `ecommerce:read`
@@ -5701,7 +6206,7 @@ Required scope | `cms:write`
-
```python
-from webflow import Webflow, StaticField
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -5709,16 +6214,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.fields.create(
- collection_id="580e63fc8c9a982ac9b8b745",
- request=StaticField(
- id="562ac0395358780a1f5e6fbc",
- is_editable=True,
- is_required=False,
- type="RichText",
- display_name="Post Body",
- help_text="Add the body of your post here",
- ),
+client.orders.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ order_id="5e8518516e147040726cc415",
)
```
@@ -5735,7 +6233,7 @@ client.collections.fields.create(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -5743,7 +6241,7 @@ client.collections.fields.create(
-
-**request:** `FieldCreate`
+**order_id:** `str` — Unique identifier for an Order
@@ -5763,7 +6261,7 @@ client.collections.fields.create(
-client.collections.fields.delete(...)
+client.orders.update(...) -> Order
-
@@ -5775,9 +6273,11 @@ client.collections.fields.create(
-
-Delete a custom field in a collection. This endpoint does not currently support bulk deletion.
+This API lets you update the fields, `comment`, `shippingProvider`,
+and/or `shippingTracking` for a given order. All three fields can be
+updated simultaneously or independently.
-Required scope | `cms:write`
+Required scope | `ecommerce:write`
@@ -5800,9 +6300,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.fields.delete(
- collection_id="580e63fc8c9a982ac9b8b745",
- field_id="580e63fc8c9a982ac9b8b745",
+client.orders.update(
+ site_id="580e63e98c9a982ac9b8b741",
+ order_id="5e8518516e147040726cc415",
)
```
@@ -5819,7 +6319,7 @@ client.collections.fields.delete(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -5827,7 +6327,39 @@ client.collections.fields.delete(
-
-**field_id:** `str` — Unique identifier for a Field in a collection
+**order_id:** `str` — Unique identifier for an Order
+
+
+
+
+
+-
+
+**comment:** `typing.Optional[str]` — Arbitrary data for your records
+
+
+
+
+
+-
+
+**shipping_provider:** `typing.Optional[str]` — Company or method used to ship order
+
+
+
+
+
+-
+
+**shipping_tracking:** `typing.Optional[str]` — Tracking number for order shipment
+
+
+
+
+
+-
+
+**shipping_tracking_url:** `typing.Optional[str]` — URL to track order shipment
@@ -5847,7 +6379,7 @@ client.collections.fields.delete(
-client.collections.fields.update(...) -> Field
+client.orders.update_fulfill(...) -> Order
-
@@ -5859,9 +6391,9 @@ client.collections.fields.delete(
-
-Update a custom field in a collection.
+Updates an order's status to fulfilled
-Required scope | `cms:write`
+Required scope | `ecommerce:write`
@@ -5884,12 +6416,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.fields.update(
- collection_id="580e63fc8c9a982ac9b8b745",
- field_id="580e63fc8c9a982ac9b8b745",
- is_required=False,
- display_name="Post Body",
- help_text="Add the body of your post here",
+client.orders.update_fulfill(
+ site_id="580e63e98c9a982ac9b8b741",
+ order_id="5e8518516e147040726cc415",
)
```
@@ -5906,23 +6435,7 @@ client.collections.fields.update(
-
-**collection_id:** `str` — Unique identifier for a Collection
-
-
-
-
-
--
-
-**field_id:** `str` — Unique identifier for a Field in a collection
-
-
-
-
-
--
-
-**is_required:** `typing.Optional[bool]` — Define whether a field is required in a collection
+**site_id:** `str` — Unique identifier for a Site
@@ -5930,7 +6443,7 @@ client.collections.fields.update(
-
-**display_name:** `typing.Optional[str]` — The name of a field
+**order_id:** `str` — Unique identifier for an Order
@@ -5938,7 +6451,7 @@ client.collections.fields.update(
-
-**help_text:** `typing.Optional[str]` — Additional text to help anyone filling out this field
+**send_order_fulfilled_email:** `typing.Optional[bool]` — Whether or not the Order Fulfilled email should be sent
@@ -5958,8 +6471,7 @@ client.collections.fields.update(
-## Collections Items
-client.collections.items.list_items(...) -> CollectionItemList
+client.orders.update_unfulfill(...) -> Order
-
@@ -5971,9 +6483,9 @@ client.collections.fields.update(
-
-List of all Items within a Collection.
+Updates an order's status to unfulfilled
-Required scope | `CMS:read`
+Required scope | `ecommerce:write`
@@ -5996,15 +6508,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.list_items(
- collection_id="580e63fc8c9a982ac9b8b745",
- cms_locale_id="cmsLocaleId",
- offset=1,
- limit=1,
- name="name",
- slug="slug",
- sort_by="lastPublished",
- sort_order="asc",
+client.orders.update_unfulfill(
+ site_id="580e63e98c9a982ac9b8b741",
+ order_id="5e8518516e147040726cc415",
)
```
@@ -6021,15 +6527,7 @@ client.collections.items.list_items(
-
-**collection_id:** `str` — Unique identifier for a Collection
-
-
-
-
-
--
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**site_id:** `str` — Unique identifier for a Site
@@ -6037,7 +6535,7 @@ client.collections.items.list_items(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**order_id:** `str` — Unique identifier for an Order
@@ -6045,67 +6543,7116 @@ client.collections.items.list_items(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
-
-
--
-
-**name:** `typing.Optional[str]` — Filter by the exact name of the item(s)
-
-
--
-**slug:** `typing.Optional[str]` — Filter by the exact slug of the item
-
+
+client.orders.refund(...) -> Order
-
-**last_published:** `typing.Optional[ItemsListItemsRequestLastPublished]` — Filter by the last published date of the item(s)
-
-
-
+#### 📝 Description
-
-**sort_by:** `typing.Optional[ItemsListItemsRequestSortBy]` — Sort results by the provided value
-
-
-
-
-
-**sort_order:** `typing.Optional[ItemsListItemsRequestSortOrder]` — Sorts the results by asc or desc
-
-
-
-
-
--
+This API will reverse a Stripe charge and refund an order back to a
+customer. It will also set the order's status to `refunded`.
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
-
+Required scope | `ecommerce:write`
+#### 🔌 Usage
-
-
-
-
-client.collections.items.create_item(...) -> CollectionItem
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.orders.refund(
+ site_id="580e63e98c9a982ac9b8b741",
+ order_id="5e8518516e147040726cc415",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**order_id:** `str` — Unique identifier for an Order
+
+
+
+
+
+-
+
+**reason:** `typing.Optional[OrdersRefundRequestReason]` — The reason for the refund
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Inventory
+client.inventory.list(...) -> InventoryItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List the current inventory levels for a particular SKU item.
+
+Required scope | `ecommerce:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.inventory.list(
+ sku_collection_id="6377a7c4b7a79608c34a46f7",
+ sku_id="5e8518516e147040726cc415",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sku_collection_id:** `str` — Unique identifier for a SKU collection. Use the List Collections API to find this ID.
+
+
+
+
+
+-
+
+**sku_id:** `str` — Unique identifier for a SKU
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.inventory.update(...) -> InventoryItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Updates the current inventory levels for a particular SKU item.
+
+Updates may be given in one or two methods, absolutely or incrementally.
+- Absolute updates are done by setting `quantity` directly.
+- Incremental updates are by specifying the inventory delta in `updateQuantity` which is then added to the `quantity` stored on the server.
+
+Required scope | `ecommerce:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.inventory.update(
+ sku_collection_id="6377a7c4b7a79608c34a46f7",
+ sku_id="5e8518516e147040726cc415",
+ inventory_type="infinite",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sku_collection_id:** `str` — Unique identifier for a SKU collection. Use the List Collections API to find this ID.
+
+
+
+
+
+-
+
+**sku_id:** `str` — Unique identifier for a SKU
+
+
+
+
+
+-
+
+**inventory_type:** `InventoryUpdateRequestInventoryType` — infinite or finite
+
+
+
+
+
+-
+
+**update_quantity:** `typing.Optional[float]` — Adds this quantity to currently store quantity. Can be negative.
+
+
+
+
+
+-
+
+**quantity:** `typing.Optional[float]` — Immediately sets quantity to this value.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Ecommerce
+client.ecommerce.get_settings(...) -> EcommerceSettings
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Retrieve ecommerce settings for a site.
+
+Required scope | `ecommerce:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.ecommerce.get_settings(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Analyze Reports
+client.analyze.reports.traffic(...) -> TrafficResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns a daily time series of a single metric — sessions, users, or page views — over a time window.
+
+Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.traffic(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ metric_scope="session",
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**metric_scope:** `TrafficMetricScope` — The unit each `count` data point is measured in.
+
+
+
+
+
+-
+
+**bucket_time_zone:** `AnalyzeBucketTimeZone` — IANA time zone used to align daily bucket boundaries.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsTrafficRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[TrafficFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TrafficFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.top_pages(...) -> TopPagesResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the most-visited pages over a time window, ranked by `sortBy` (sessions, users, or page views).
+
+Each row carries all three scope counts; `sortBy` only governs ordering. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+Set `timeseries[bucketTimeZone]` to attach a daily page view `timeseries` to each row. Bucket counts are always page views regardless of `sortBy`. Row-level counts honor the requested sort, but the timeseries does not.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.top_pages(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ sort_by="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[TopPagesSortBy]` — Metric used to rank rows in the response, descending. Defaults to `session`.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+
+
+
+
+-
+
+**timeseries:** `typing.Optional[AnalyzeDailyTimeseriesQuery]` — Include a daily page view `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-page timeseries data.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsTopPagesRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[TopPagesFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopPagesFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.top_dimensions(...) -> TopDimensionsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the top values within a chosen `dimension` — top countries, top traffic sources, top campaigns, top audiences, and so on — over a time window, ranked by sessions or users.
+
+Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.top_dimensions(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ dimension="country",
+ metric_scope="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**dimension:** `TopDimensionsDimension` — The dimension whose top values are ranked. See `TopDimensionsDimension` for the supported values.
+
+
+
+
+
+-
+
+**metric_scope:** `TopDimensionsMetricScope` — The unit each row's `count` is measured in — sessions or users.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of rows to return. Defaults to `25`, up to a maximum of `100`.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsTopDimensionsRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[TopDimensionsFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopDimensionsFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.top_events(...) -> TopEventsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the top events over a time window, ranked by how often they occurred.
+
+Events are counted individually, not rolled up into sessions, users, or page views — so this report has no `metricScope`. Each row's `count` is how many times the event occurred. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+Set `timeseries[bucketTimeZone]` to attach a daily event count `timeseries` to each row.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.top_events(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+
+
+
+
+-
+
+**timeseries:** `typing.Optional[AnalyzeDailyTimeseriesQuery]` — Include a daily event count `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-event timeseries data.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsTopEventsRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[TopEventsFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopEventsFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.time_on_page(...) -> TimeOnPageResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the average time on page over a time window — as a single aggregate value, or bucketed by day or week when `timeseries` is supplied.
+
+Choose how the average is computed with `metricScope` (per session, user, or page view). Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.time_on_page(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ metric_scope="session",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**metric_scope:** `TimeOnPageMetricScope` — How the average time on page is computed — per session, user, or page view.
+
+
+
+
+
+-
+
+**timeseries:** `typing.Optional[TimeOnPageTimeseriesQuery]` — Include bucketed average time data using the supplied granularity and IANA time zone. Omit this parameter to return a single aggregate value for the requested window.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsTimeOnPageRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[TimeOnPageFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TimeOnPageFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.scroll_depth(...) -> ScrollDepthResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the scroll-depth distribution for the site over a time window. Each row reports the scroll depth (in pixels) that a share of page views reached, at fixed breakpoints of 75%, 50%, and 25% of page views.
+
+Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.scroll_depth(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsScrollDepthRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[ScrollDepthFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `ScrollDepthFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.browser_window_height(...) -> BrowserWindowHeightResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the average browser window height (in CSS pixels) for the site over a time window. The response wraps the single aggregate in a one-element `data` array for SDK uniformity with other Analyze reports.
+
+Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.browser_window_height(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsBrowserWindowHeightRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[BrowserWindowHeightFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `BrowserWindowHeightFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.reports.page_flow(...) -> PageFlowResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the pages visitors viewed immediately before or after a required anchor page over a time window, ranked by sessions, users, or page views.
+
+For a CMS template instance, provide both `collectionId` and `itemSlug` with `pageId`. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.reports.page_flow(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ direction="previous",
+ metric_scope="session",
+ page_id="65f1b2c4a8d3e5f7a9c1b2d3",
+ collection_id="66abc1234567890abcdef012",
+ item_slug="anchor-post",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**direction:** `PageFlowDirection` — Whether to return pages viewed immediately before or after the anchor page.
+
+
+
+
+
+-
+
+**metric_scope:** `PageFlowMetricScope` — The unit each row's `count` is measured in.
+
+
+
+
+
+-
+
+**page_id:** `str` — Identifier of the anchor Webflow page.
+
+
+
+
+
+-
+
+**collection_id:** `typing.Optional[str]` — Identifier of the CMS collection for an anchor template-page instance. Must be provided together with `itemSlug`, or both must be omitted.
+
+
+
+
+
+-
+
+**item_slug:** `typing.Optional[str]` — Slug of the CMS item for an anchor template-page instance. Must be provided together with `collectionId`, or both must be omitted.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of rows to return. Defaults to `10`, up to a maximum of `100`.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[ReportsPageFlowRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[PageFlowFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `PageFlowFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Analyze Goals
+client.analyze.goals.list_goals(...) -> GoalsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns the goals configured for a site. This endpoint returns a list of goals, not a report. It takes no time window, filters, or other query parameters, and unknown query parameters return `400 analyze_input_validation`.
+
+Each goal includes its type (`goalType`) and whether it's a site goal (`isSiteGoal`). Integration goals also include `integrationSource`, and HubSpot or Marketo form goals include the `formId`. Use a goal's `goalId` with [Get Goal Performance](/data/reference/analyze/goal-performance) to get its daily conversions.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.goals.list_goals(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.analyze.goals.goal_performance(...) -> GoalPerformanceResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns daily conversion counts for one goal over a time window, together with the goal's configuration (the same object [List Goals](/data/reference/analyze/list-goals) returns). Conversions are always counted as unique conversions.
+
+Days are bucketed in `bucketTimeZone`, which defaults to `UTC`. An unknown `goal_id` returns `404`. A goal with no conversions in the window returns an empty `data` array. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+This endpoint requires a workspace with the Analyze add-on.
+
+Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.analyze.goals.goal_performance(
+ site_id="580e63e98c9a982ac9b8b741",
+ goal_id="198020177",
+ start_time=datetime.datetime.fromisoformat("2026-04-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-04-08T00:00:00+00:00"),
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**goal_id:** `str` — Identifier of the goal, from `goalId` in [List Goals](/data/reference/analyze/list-goals). A numeric string, not a Webflow object ID.
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+
+
+
+
+-
+
+**bucket_time_zone:** `typing.Optional[AnalyzeBucketTimeZone]` — IANA time zone used to align daily bucket boundaries. Defaults to `UTC`.
+
+
+
+
+
+-
+
+**device_type:** `typing.Optional[GoalsGoalPerformanceRequestDeviceType]` — Restrict the report to a single device type.
+
+
+
+
+
+-
+
+**country:** `typing.Optional[str]` — Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+
+
+
+
+-
+
+**page_path:** `typing.Optional[str]` — Restrict the report to a single page path.
+
+
+
+
+
+-
+
+**traffic_source:** `typing.Optional[str]` — Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+
+
+
+
+-
+
+**referrer:** `typing.Optional[str]` — Restrict the report to a single referrer domain.
+
+
+
+
+
+-
+
+**browser:** `typing.Optional[str]` — Restrict the report to a single browser.
+
+
+
+
+
+-
+
+**utm_campaign:** `typing.Optional[str]` — Restrict the report to a single `utm_campaign` value.
+
+
+
+
+
+-
+
+**utm_medium:** `typing.Optional[str]` — Restrict the report to a single `utm_medium` value.
+
+
+
+
+
+-
+
+**utm_source:** `typing.Optional[str]` — Restrict the report to a single `utm_source` value.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[GoalPerformanceFilter]`
+
+Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `GoalPerformanceFilter` schema for the full list of supported dimensions.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Collections Fields
+client.collections.fields.create(...) -> FieldCreate
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Create a custom field in a collection.
+
+Field validation is currently not available through the API.
+
+Bulk creation of fields is not supported with this endpoint. To add multiple fields at once, include them when you [create the collection.](/data/v2.0.0/reference/cms/collections/create)
+
+Required scope | `cms:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, StaticField
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.fields.create(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ request=StaticField(
+ id="562ac0395358780a1f5e6fbc",
+ is_editable=True,
+ is_required=False,
+ type="RichText",
+ display_name="Post Body",
+ help_text="Add the body of your post here",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**request:** `FieldCreate`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.fields.delete(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete a custom field in a collection. This endpoint does not currently support bulk deletion.
+
+Required scope | `cms:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.fields.delete(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ field_id="580e63fc8c9a982ac9b8b745",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**field_id:** `str` — Unique identifier for a Field in a collection
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.fields.update(...) -> Field
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Update a custom field in a collection.
+
+Required scope | `cms:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.fields.update(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ field_id="580e63fc8c9a982ac9b8b745",
+ is_required=False,
+ display_name="Post Body",
+ help_text="Add the body of your post here",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**field_id:** `str` — Unique identifier for a Field in a collection
+
+
+
+
+
+-
+
+**is_required:** `typing.Optional[bool]` — Define whether a field is required in a collection
+
+
+
+
+
+-
+
+**display_name:** `typing.Optional[str]` — The name of a field
+
+
+
+
+
+-
+
+**help_text:** `typing.Optional[str]` — Additional text to help anyone filling out this field
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Collections Items
+client.collections.items.list_items(...) -> CollectionItemList
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+List of all Items within a Collection.
+
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
+Required scope | `CMS:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.list_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ cms_locale_id="cmsLocaleId",
+ offset=1,
+ limit=1,
+ name="name",
+ slug="slug",
+ sort_by="createdOn",
+ sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**name:** `typing.Optional[str]` — Filter by the exact name of the item(s)
+
+
+
+
+
+-
+
+**slug:** `typing.Optional[str]` — Filter by the exact slug of the item
+
+
+
+
+
+-
+
+**created_on:** `typing.Optional[ItemsListItemsRequestCreatedOn]` — Filter by the creation date of the item(s)
+
+
+
+
+
+-
+
+**last_published:** `typing.Optional[ItemsListItemsRequestLastPublished]` — Filter by the last published date of the item(s)
+
+
+
+
+
+-
+
+**last_updated:** `typing.Optional[ItemsListItemsRequestLastUpdated]` — Filter by the last updated date of the item(s)
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]]`
+
+Filter collection items by custom field values. Use bracket notation:
+`filter[][]=`.
+
+Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+More filter terms can increase request latency.
+
+Supported operators by field type:
+
+| Field type | Supported operators |
+| --- | --- |
+| `id` | `eq`, `ne`, `in`, `nin` |
+| `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+| `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+| `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+| `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+| `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `Option` | `eq`, `ne`, `in`, `nin` |
+| `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+`contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+`exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+Value formats:
+
+| Field type | Value format |
+| --- | --- |
+| `Number` | A valid number, such as `10` or `12.5` |
+| `Switch` | `true` or `false` |
+| `DateTime` | ISO 8601 date-time string |
+| `id`, `Reference` | 24-character item ID |
+| `Option` | Option ID |
+| `in`, `nin` | Comma-separated list, up to 100 values |
+
+Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[ItemsListItemsRequestSortBy]` — Sort results by the provided value
+
+
+
+
+
+-
+
+**sort_order:** `typing.Optional[ItemsListItemsRequestSortOrder]` — Sorts the results by asc or desc
+
+
+
+
+
+-
+
+**sort:** `typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]]`
+
+Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+- Example: `sort[price]=desc`
+- Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+- Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+- Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+
+
+
+
+-
+
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.create_item(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Create Item(s) in a Collection.
+
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which accepts both request shapes below and can create an item in several locales under one ID. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how each shape maps.
+
+
+This endpoint accepts two request shapes, and a request must use one or the other:
+
+- **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+- **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+```json
+{
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+}
+```
+
+A request that carries both `fieldData` and `items` returns a `400`.
+
+To create an item in several locales under one ID, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items).
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItemPostSingle, CollectionItemPostSingleFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.create_item(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ request=CollectionItemPostSingle(
+ is_archived=False,
+ is_draft=False,
+ field_data=CollectionItemPostSingleFieldData(
+ name="The Hitchhiker\'s Guide to the Galaxy",
+ slug="hitchhikers-guide-to-the-galaxy",
+ ),
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**request:** `ItemsCreateItemRequestBody`
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.delete_items(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete Items from a Collection.
+
+Items will only be deleted in the primary locale unless a `cmsLocaleId` is included in the request.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+from webflow.collections.items import ItemsDeleteItemsRequestItemsItem
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.delete_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ items=[
+ ItemsDeleteItemsRequestItemsItem(
+ id="580e64008c9a982ac9b8b754",
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**items:** `typing.List[ItemsDeleteItemsRequestItemsItem]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.update_items(...) -> ItemsUpdateItemsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Update a single item or multiple items in a Collection.
+
+The limit for this endpoint is 100 items.
+
+Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItemWithIdInput, CollectionItemWithIdInputFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.update_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ items=[
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5ea6",
+ cms_locale_id="66f6e966c9e1dc700a857ca5",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Ne Paniquez Pas",
+ slug="ne-paniquez-pas",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5ea6",
+ cms_locale_id="66f6e966c9e1dc700a857ca4",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="No Entrar en Pánico",
+ slug="no-entrar-en-panico",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5eaa",
+ cms_locale_id="66f6e966c9e1dc700a857ca5",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Au Revoir et Merci pour Tous les Poissons",
+ slug="au-revoir-et-merci",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5eaa",
+ cms_locale_id="66f6e966c9e1dc700a857ca4",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Hasta Luego y Gracias por Todo el Pescado",
+ slug="hasta-luego-y-gracias",
+ ),
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**items:** `typing.Optional[typing.List[CollectionItemWithIdInput]]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.list_items_live(...) -> CollectionItemList
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+List all published items in a collection.
+
+
+ Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
+Required scope | `CMS:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.list_items_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ cms_locale_id="cmsLocaleId",
+ offset=1,
+ limit=1,
+ name="name",
+ slug="slug",
+ sort_by="createdOn",
+ sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**name:** `typing.Optional[str]` — Filter by the exact name of the item(s)
+
+
+
+
+
+-
+
+**slug:** `typing.Optional[str]` — Filter by the exact slug of the item
+
+
+
+
+
+-
+
+**created_on:** `typing.Optional[ItemsListItemsLiveRequestCreatedOn]` — Filter by the creation date of the item(s)
+
+
+
+
+
+-
+
+**last_published:** `typing.Optional[ItemsListItemsLiveRequestLastPublished]` — Filter by the last published date of the item(s)
+
+
+
+
+
+-
+
+**last_updated:** `typing.Optional[ItemsListItemsLiveRequestLastUpdated]` — Filter by the last updated date of the item(s)
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]]`
+
+Filter collection items by custom field values. Use bracket notation:
+`filter[][]=`.
+
+Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+More filter terms can increase request latency.
+
+Supported operators by field type:
+
+| Field type | Supported operators |
+| --- | --- |
+| `id` | `eq`, `ne`, `in`, `nin` |
+| `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+| `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+| `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+| `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+| `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+| `Option` | `eq`, `ne`, `in`, `nin` |
+| `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+`contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+`exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+Value formats:
+
+| Field type | Value format |
+| --- | --- |
+| `Number` | A valid number, such as `10` or `12.5` |
+| `Switch` | `true` or `false` |
+| `DateTime` | ISO 8601 date-time string |
+| `id`, `Reference` | 24-character item ID |
+| `Option` | Option ID |
+| `in`, `nin` | Comma-separated list, up to 100 values |
+
+Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[ItemsListItemsLiveRequestSortBy]` — Sort results by the provided value
+
+
+
+
+
+-
+
+**sort_order:** `typing.Optional[ItemsListItemsLiveRequestSortOrder]` — Sorts the results by asc or desc
+
+
+
+
+
+-
+
+**sort:** `typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]]`
+
+Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+- Example: `sort[price]=desc`
+- Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+- Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+- Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+
+
+
+
+-
+
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.create_item_live(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Create item(s) in a collection that will be immediately published to the live site.
+
+This endpoint accepts two request shapes, and a request must use one or the other:
+
+- **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+- **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+```json
+{
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+}
+```
+
+A request that carries both `fieldData` and `items` returns a `400`.
+
+To create items in several locales, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items) and then [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item). Create Items writes staged content only.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItem, CollectionItemFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.create_item_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ request=CollectionItem(
+ is_archived=False,
+ is_draft=False,
+ field_data=CollectionItemFieldData(
+ name="The Hitchhiker\'s Guide to the Galaxy",
+ slug="hitchhikers-guide-to-the-galaxy",
+ ),
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**request:** `ItemsCreateItemLiveRequestBody`
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.delete_items_live(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Unpublish up to 100 items from the live site and set the `isDraft` property to `true`.
+
+Items will only be unpublished in the primary locale unless a `cmsLocaleId` is included in the request.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+from webflow.collections.items import ItemsDeleteItemsLiveRequestItemsItem
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.delete_items_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ items=[
+ ItemsDeleteItemsLiveRequestItemsItem(
+ id="580e64008c9a982ac9b8b754",
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**items:** `typing.List[ItemsDeleteItemsLiveRequestItemsItem]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.update_items_live(...) -> CollectionItemListNoPagination
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Update a single published item or multiple published items (up to 100) in a Collection
+
+Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItemWithIdInput, CollectionItemWithIdInputFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.update_items_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ items=[
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5ea6",
+ cms_locale_id="66f6e966c9e1dc700a857ca5",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Ne Paniquez Pas",
+ slug="ne-paniquez-pas",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5ea6",
+ cms_locale_id="66f6e966c9e1dc700a857ca4",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="No Entrar en Pánico",
+ slug="no-entrar-en-panico",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5eaa",
+ cms_locale_id="66f6e966c9e1dc700a857ca5",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Au Revoir et Merci pour Tous les Poissons",
+ slug="au-revoir-et-merci",
+ ),
+ ),
+ CollectionItemWithIdInput(
+ id="66f6ed9576ddacf3149d5eaa",
+ cms_locale_id="66f6e966c9e1dc700a857ca4",
+ field_data=CollectionItemWithIdInputFieldData(
+ name="Hasta Luego y Gracias por Todo el Pescado",
+ slug="hasta-luego-y-gracias",
+ ),
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**items:** `typing.Optional[typing.List[CollectionItemWithIdInput]]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.create_items(...) -> BulkCollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which does everything this endpoint does and can also add locale variants to an existing item. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how the request shape maps.
+
+
+
+ - This endpoint can create up to 100 items in a request.
+ - If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
+
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+from webflow.collections.items import SingleCmsItem
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.create_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ cms_locale_ids=[
+ "66f6e966c9e1dc700a857ca3",
+ "66f6e966c9e1dc700a857ca4",
+ "66f6e966c9e1dc700a857ca5"
+ ],
+ is_archived=False,
+ is_draft=False,
+ field_data=SingleCmsItem(
+ name="Don’t Panic",
+ slug="dont-panic",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**field_data:** `CreateBulkCollectionItemRequestBodyFieldData`
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**cms_locale_ids:** `typing.Optional[typing.List[str]]` — Array of identifiers for the locales where the item will be created
+
+
+
+
+
+-
+
+**is_archived:** `typing.Optional[bool]` — Indicates whether the item is archived.
+
+
+
+
+
+-
+
+**is_draft:** `typing.Optional[bool]` — Indicates whether the item is in draft state.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.insert_items(...) -> ItemsInsertItemsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Create one or more staged items in a CMS Collection.
+
+Send an `items` array with `fieldData` for each item. By default, new items are created in the primary locale. To choose other locales or add variants to an existing item, expand the options below.
+
+- **Publishing:** Set `isDraft: false` to queue an item for the next site publish. Items are created as drafts (`isDraft: true`) by default. To publish individual items, use [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item).
+- **Request limit:** Up to 100 items, counting every locale variant across all entries. One item in three locales counts as three; an item in the primary locale only counts as one.
+
+Required scope | `cms:write`
+
+
+
+ Each new item gets one ID. Its locale variants share that ID and the same `fieldData`.
+
+ - **Selected locales:** Set `cmsLocaleIds` to the locale IDs you want to create the item in.
+ - **All site locales:** Set `allCmsLocales: true` to include every current site locale, including the primary locale, without listing IDs.
+ - **Primary locale only:** Omit both `cmsLocaleIds` and `allCmsLocales`.
+
+ Use only one locale selector per entry. `allCmsLocales` is available only for new items and accepts only `true`; omit it instead of sending `false`.
+
+
+
+ Set `id` to an existing item's ID and provide the new locales in `cmsLocaleIds`. The variants reuse that ID. The item must exist and must not already have a variant in any requested locale.
+
+ Send the source content in `fieldData` as a starting point for translators. To provide different content per locale, send multiple entries with the same `id`, each with one locale in `cmsLocaleIds` and its own `fieldData`.
+
+
+
+ These requests return a `400`:
+
+ - Combining `allCmsLocales` with `cmsLocaleIds` (even an empty array) or `id`.
+ - Setting `allCmsLocales` to `false`, a string, or `null`.
+ - Sending unrecognized properties, including singular `cmsLocaleId`. Use `cmsLocaleIds` (plural).
+ - Repeating a locale within one entry, or the same `id`/locale pair across entries.
+
+ Check the response's `message` and `details` for validation errors. Some errors identify an entry, such as `items[2].cmsLocaleIds`. Invalid locale selector combinations return a message with an empty `details` array.
+
+
+
+Moving from `POST /items` or `POST /items/bulk`? See [Creating collection items](/data/docs/working-with-the-cms/create-items) for request mappings.
+
+Rich Text fields can include Webflow components. See [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for markup and constraints.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, InsertCollectionItem, InsertCollectionItemFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.insert_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ items=[
+ InsertCollectionItem(
+ field_data=InsertCollectionItemFieldData(
+ name="Mostly Harmless",
+ slug="mostly-harmless",
+ ),
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**items:** `typing.List[InsertCollectionItem]` — The items to create. A request can create up to 100 items, counted as the sum of the locales across every entry. An `allCmsLocales: true` entry counts once per current site locale; a new-item entry with neither locale selector counts as one.
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.get_item(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Get details of a selected Collection Item.
+
+Required scope | `CMS:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.get_item(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+
+
+
+
+-
+
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.delete_item(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete an item from a collection.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.delete_item(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ cms_locale_id="cmsLocaleId",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.update_item(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Update a selected Item in a Collection.
+
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItemPatchSingleFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.update_item(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ skip_invalid_files=True,
+ is_archived=False,
+ is_draft=False,
+ field_data=CollectionItemPatchSingleFieldData(
+ name="The Hitchhiker\'s Guide to the Galaxy",
+ slug="hitchhikers-guide-to-the-galaxy",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**request:** `CollectionItemPatchSingle`
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.get_item_live(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Get details of a selected Collection live Item.
+
+
+ Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+
+Required scope | `CMS:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.get_item_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+
+
+
+
+-
+
+**translatable:** `typing.Optional[str]`
+
+Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+`?localeId={primary locale id}&translatable={target locale id}`
+
+Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.delete_item_live(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Unpublish a live item from the site and set the `isDraft` property to `true`.
+
+For bulk unpublishing, please use [this endpoint.](/data/v2.0.0/reference/cms/collection-items/live-items/delete-items-live)
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.delete_item_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ cms_locale_id="cmsLocaleId",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.update_item_live(...) -> CollectionItem
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
+Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
+
+Required scope | `CMS:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, CollectionItemPatchSingleFieldData
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.update_item_live(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ item_id="580e64008c9a982ac9b8b754",
+ skip_invalid_files=True,
+ is_archived=False,
+ is_draft=False,
+ field_data=CollectionItemPatchSingleFieldData(
+ name="The Hitchhiker\'s Guide to the Galaxy",
+ slug="hitchhikers-guide-to-the-galaxy",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**item_id:** `str` — Unique identifier for an Item
+
+
+
+
+
+-
+
+**request:** `CollectionItemPatchSingle`
+
+
+
+
+
+-
+
+**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.collections.items.publish_item(...) -> ItemsPublishItemResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Publish an item or multiple items.
+
+Publishing a draft item automatically sets `isDraft` to `false`.
+You don't need to update the item's draft status before calling this endpoint.
+
+Required scope | `cms:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+from webflow.collections.items import ItemIDs
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.collections.items.publish_item(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ request=ItemIDs(
+ item_ids=[
+ "643fd856d66b6528195ee2ca",
+ "643fd856d66b6528195ee2cb",
+ "643fd856d66b6528195ee2cc"
+ ],
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**collection_id:** `str` — Unique identifier for a Collection
+
+
+
+
+
+-
+
+**request:** `ItemsPublishItemRequest`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Optimize Optimizations
+client.optimize.optimizations.list(...) -> OptimizationListResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Lists the optimizations for an `optimizeId`, with each one's type, status, intervals, and current goals. Returns live and draft optimizations unless you set `status`.
+
+Each report covers a date range within a single interval of an optimization. Use an interval's `start` and `end` to choose a report's `intervalStart` and `intervalEnd`.
+
+Each interval lists its goals as they were when it started. `currentGoals` lists the optimization's goals as they're set up now, and each goal's `source` shows where it comes from. A report accepts a `goalId` from either list.
+
+Results are sorted by `optimizationId`, and each optimization's intervals are listed oldest first. To get the next page, pass `nextCursor` as `cursor`.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.list(
+ optimize_id="117420042",
+ status=[
+ "live",
+ "draft"
+ ],
+ page_size=1,
+ cursor="c2xhcnRpYmFydGZhc3Q",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**status:** `typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]]`
+
+The statuses to include. Defaults to `live` and `draft`. Pass repeated values (`status=live&status=draft`) or a comma-separated list (`status=live,draft`).
+
+A `cursor` keeps the statuses it was created with. When you pass a `cursor`, omit `status` or send the same statuses.
+
+
+
+
+
+-
+
+**page_size:** `typing.Optional[int]` — The maximum number of results to return. Defaults to `100`, up to `200`.
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — The `nextCursor` from a previous response, to get the next page.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.optimize.optimizations.summary(...) -> OptimizationSummaryResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns each variation's sessions, users, conversions, and conversion rate for one goal, over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+Value goals also include each variation's total value and average conversion value. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.summary(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+ goal_id="198042421",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**goal_id:** `typing.Optional[str]`
+
+The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[OptimizeFilter]`
+
+Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.optimize.optimizations.audience_insights(...) -> OptimizationAudienceInsightsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns each variation's sessions, users, conversions, and conversion rate for each segment of one dimension, for one goal over a date range within one interval. Choose the dimension with `groupBy`.
+
+Each segment also shows how the variation is trending against the base variation in that segment, and how confident that trend is. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.audience_insights(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+ group_by="country",
+ goal_id="198042421",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**group_by:** `str` — The dimension to split results by, from [List Dimensions](/data/reference/optimize/list-dimensions). Accepts any dimension except `urlParameter`.
+
+
+
+
+
+-
+
+**goal_id:** `typing.Optional[str]`
+
+The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[OptimizeFilter]`
+
+Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.optimize.optimizations.trend(...) -> OptimizationTrendResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Returns each variation's sessions, users, conversions, and conversion rate by day or week, for one goal over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+Buckets use the account's time zone, returned as `timeZone`. For AI-optimized optimizations, `strength` covers the whole date range and is the same in every bucket. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.trend(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+ granularity="daily",
+ goal_id="198042421",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**granularity:** `OptimizeTrendGranularity` — Whether to bucket results by day or by week.
+
+
+
+
+
+-
+
+**goal_id:** `typing.Optional[str]`
+
+The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[OptimizeFilter]`
+
+Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Optimize Optimizations Goals
+client.optimize.optimizations.goals.list(...) -> OptimizationGoalsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Lists an optimization's goals in two lists. `goals` holds the goals of the interval that contains the requested date range, as they were when it started, with its target goals marked. `currentGoals` holds the optimization's goals as they're set up now, with where each comes from. Use a `goalId` from either list to choose the goal a report measures.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.goals.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Unlike the report endpoints, this endpoint has no 18-month limit.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Optimize Optimizations Dimensions
+client.optimize.optimizations.dimensions.list(...) -> OptimizationDimensionsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Lists the dimensions you can group or filter an optimization's reports by, for a date range within one interval. Use a dimension's `id` in `groupBy`, in `filter`, or to list its values.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.dimensions.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.optimize.optimizations.dimensions.list_values(...) -> OptimizationDimensionValuesResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Lists the values seen for one dimension over a date range within one interval, with how often each appeared. Use a `value` in `filter` to narrow a report.
+
+Values are sorted by `eventCount`, highest first. To get the next page, pass `nextCursor` as `cursor`. Counts can change between requests, so a value can move to a different page while you page through the list.
+
+This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.optimize.optimizations.dimensions.list_values(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ dimension_id="country",
+ interval_start=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ interval_end=datetime.datetime.fromisoformat("2026-09-15T00:00:00+00:00"),
+ page_size=1,
+ cursor="ZmpvcmRz",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**optimize_id:** `str` — The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+
+
+
+
+-
+
+**optimization_id:** `str` — The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+
+
+
+
+-
+
+**dimension_id:** `str` — The dimension `id`, from [List Dimensions](/data/reference/optimize/list-dimensions). URL-encode it, because dimension IDs can contain `:`, `.`, and spaces. Unlike `groupBy`, this accepts `urlParameter`.
+
+
+
+
+
+-
+
+**interval_start:** `datetime.datetime` — Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+
+
+
+
+-
+
+**interval_end:** `datetime.datetime` — End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**filter:** `typing.Optional[OptimizeFilter]`
+
+Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+
+
+
+
+-
+
+**page_size:** `typing.Optional[int]` — The maximum number of results to return. Defaults to `100`, up to `200`.
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — The `nextCursor` from a previous response, to get the next page. Send the same dimension, `intervalStart`, `intervalEnd`, and `filter` as the original request. Note that `pageSize` can differ between requests.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Pages Scripts
+client.pages.scripts.get_custom_code(...) -> ScriptApplyList
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get all scripts applied to a page.
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.pages.scripts.get_custom_code(
+ page_id="63c720f9347c2139b248e552",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**page_id:** `str` — Unique identifier for a Page
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.pages.scripts.upsert_custom_code(...) -> ScriptApplyList
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Apply registered scripts to a page. If you have multiple scripts your App needs to apply or maintain on a page, ensure they are always included in the request body for this endpoint. To remove individual scripts, simply call this endpoint without the script in the request body.
+
+
+ To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, ScriptApply
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.pages.scripts.upsert_custom_code(
+ page_id="63c720f9347c2139b248e552",
+ scripts=[
+ ScriptApply(
+ id="cms_slider",
+ location="header",
+ version="1.0.0",
+ attributes={
+ "my-attribute": "some-value"
+ },
+ ),
+ ScriptApply(
+ id="alert",
+ location="header",
+ version="0.0.1",
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**page_id:** `str` — Unique identifier for a Page
+
+
+
+
+
+-
+
+**request:** `ScriptApplyList`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.pages.scripts.delete_custom_code(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Remove all scripts from a page applied by the App. This endpoint will not remove scripts from the site's registered scripts.
+
+To remove individual scripts applied by the App, use the [Add/Update Custom Code](/data/reference/custom-code/custom-code-pages/upsert-custom-code) endpoint.
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.pages.scripts.delete_custom_code(
+ page_id="63c720f9347c2139b248e552",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**page_id:** `str` — Unique identifier for a Page
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Sites Redirects
+client.sites.redirects.list(...) -> Redirects
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Fetch a list of all 301 redirect rules configured for a specific site.
+
+Use this endpoint to review, audit, or manage the redirection rules that control how traffic is rerouted on your site.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.redirects.list(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.redirects.create(...) -> Redirect
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Add a new 301 redirection rule to a site.
+
+This endpoint allows you to define a source path (`fromUrl`) and its corresponding destination path (`toUrl`), which will dictate how traffic is rerouted on your site. This is useful for managing site changes, restructuring URLs, or handling outdated links.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `sites:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.redirects.create(
+ site_id="580e63e98c9a982ac9b8b741",
+ id="42e1a2b7aa1a13f768a0042a",
+ from_url="/mostly-harmless",
+ to_url="/earth",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request:** `Redirect`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.redirects.delete(...) -> Redirects
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Remove a 301 redirection rule from a site.
+
+This is useful for cleaning up outdated or unnecessary redirects, ensuring that your site's routing behavior remains efficient and up-to-date.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `sites:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.redirects.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ redirect_id="66c4cb9a20cac35ed19500e6",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**redirect_id:** `str` — Unique identifier site redirect
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.redirects.update(...) -> Redirect
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Update a 301 redirection rule from a site.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `sites:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.redirects.update(
+ site_id="580e63e98c9a982ac9b8b741",
+ redirect_id="66c4cb9a20cac35ed19500e6",
+ id="42e1a2b7aa1a13f768a0042a",
+ from_url="/mostly-harmless",
+ to_url="/earth",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**redirect_id:** `str` — Unique identifier site redirect
+
+
+
+
+
+-
+
+**request:** `Redirect`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Sites Plans
+client.sites.plans.get_site_plan(...) -> SitePlan
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get site plan details for the specified Site.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.plans.get_site_plan(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Sites RobotsTxt
+client.sites.robots_txt.get(...) -> Robots
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Retrieve the robots.txt configuration for various user agents.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `site_config:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.robots_txt.get(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.robots_txt.put(...) -> Robots
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Replace the `robots.txt` configuration for various user agents.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_config:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, RobotsRulesItem
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.robots_txt.put(
+ site_id="580e63e98c9a982ac9b8b741",
+ rules=[
+ RobotsRulesItem(
+ user_agent="googlebot",
+ allows=[
+ "/public"
+ ],
+ disallows=[
+ "/vogon-poetry",
+ "/total-perspective-vortex"
+ ],
+ )
+ ],
+ sitemap="https://heartofgold.ship/sitemap.xml",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request:** `Robots`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.robots_txt.delete(...) -> Robots
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Remove specific rules for a user-agent in your `robots.txt` file. To delete all rules for a user-agent, provide an empty rule set. This will remove the user-agent's entry entirely, leaving it subject to your site's default crawling behavior.
+
+**Note:** Deleting a user-agent with no rules will make the user-agent's access unrestricted unless other directives apply.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `site_config:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, RobotsRulesItem
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.robots_txt.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ rules=[
+ RobotsRulesItem(
+ user_agent="*",
+ allows=[
+ "/public"
+ ],
+ disallows=[
+ "/bubbles"
+ ],
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request:** `Robots`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.robots_txt.patch(...) -> Robots
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Update the `robots.txt` configuration for various user agents.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_config:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, RobotsRulesItem
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.robots_txt.patch(
+ site_id="580e63e98c9a982ac9b8b741",
+ rules=[
+ RobotsRulesItem(
+ user_agent="googlebot",
+ allows=[
+ "/public"
+ ],
+ disallows=[
+ "/vogon-poetry",
+ "/total-perspective-vortex"
+ ],
+ )
+ ],
+ sitemap="https://heartofgold.ship/sitemap.xml",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request:** `Robots`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Sites WellKnown
+client.sites.well_known.put(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Upload a supported well-known file to a site.
+
+The current restrictions on well-known files are as follows:
+ - Each file must be smaller than 100kb
+ - Less than 30 total files
+ - Have one of the following file extensions (or no extension): `.txt`, `.json`, `.noext`
+
+
+ `.noext` is a special file extension that removes other extensions. For example, `apple-app-site-association.noext.txt` will be uploaded as `apple-app-site-association`. Use this extension for tools that have trouble uploading extensionless files.
+
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `site_config:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.well_known.put(
+ site_id="580e63e98c9a982ac9b8b741",
+ file_name="apple-app-site-association.txt",
+ file_data="{\n \"applinks\": {\n \"apps\": [],\n \"details\": [\n {\n \"appID\": \"ABCDE12345.com.example.app\",\n \"paths\": [ \"/*\", \"/some/path/*\" ]\n }\n ]\n }\n}\n",
+ content_type="application/json",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**file_name:** `str` — The name of the file
+
+
+
+
+
+-
+
+**file_data:** `str` — The contents of the file
+
+
+
+
+
+-
+
+**content_type:** `typing.Optional[WellKnownFileContentType]` — The content type of the file. Defaults to application/json
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sites.well_known.delete(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete existing well-known files from a site.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope: `site_config:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.well_known.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**file_names:** `typing.Optional[typing.List[str]]` — A list of file names to delete
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+## Sites GoogleTag
+client.sites.google_tag.list(...) -> GoogleTagIds
-
@@ -6117,12 +13664,84 @@ client.collections.items.list_items(
-
-Create Item(s) in a Collection.
+List all Google Tag IDs configured for a site, sorted by order.
+
+Required scope: `sites:read`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.google_tag.list(
+ site_id="580e63e98c9a982ac9b8b741",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
-To create items across multiple locales, please use [this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
-Required scope | `CMS:write`
+
+
+
+
+client.sites.google_tag.delete_all(...)
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete all Google Tag IDs from a site.
+
+Required scope: `sites:write`
@@ -6137,7 +13756,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItemPostSingle, CollectionItemPostSingleFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -6145,17 +13764,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.create_item(
- collection_id="580e63fc8c9a982ac9b8b745",
- skip_invalid_files=True,
- request=CollectionItemPostSingle(
- is_archived=False,
- is_draft=False,
- field_data=CollectionItemPostSingleFieldData(
- name="The Hitchhiker\'s Guide to the Galaxy",
- slug="hitchhikers-guide-to-the-galaxy",
- ),
- ),
+client.sites.google_tag.delete_all(
+ site_id="580e63e98c9a982ac9b8b741",
)
```
@@ -6172,7 +13782,7 @@ client.collections.items.create_item(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6180,15 +13790,91 @@ client.collections.items.create_item(
-
-**request:** `ItemsCreateItemRequestBody`
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+client.sites.google_tag.upsert(...) -> GoogleTagIds
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+#### 📝 Description
+
+
+-
+
+
+-
+
+Add or update Google Tag IDs for a site. Existing tags not referenced in the request are preserved. A site may have a maximum of 25 tags total.
+
+`order` is optional on input — it is auto-assigned for new tags and returned on all tags in the response.
+
+Required scope: `sites:write`
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from webflow import Webflow, GoogleTagId
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.google_tag.upsert(
+ site_id="580e63e98c9a982ac9b8b741",
+ google_tag_ids=[
+ GoogleTagId(
+ order=0,
+ display_name="Main Analytics Tag",
+ tag_id="G-1234567890",
+ )
+ ],
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**request:** `GoogleTagIds`
@@ -6208,7 +13894,7 @@ client.collections.items.create_item(
-client.collections.items.delete_items(...)
+client.sites.google_tag.delete(...) -> GoogleTagIds
-
@@ -6220,11 +13906,9 @@ client.collections.items.create_item(
-
-Delete Items from a Collection.
-
-Items will only be deleted in the primary locale unless a `cmsLocaleId` is included in the request.
+Delete a single Google Tag ID from a site. The `order` values of the remaining tags are renormalized after deletion.
-Required scope | `CMS:write`
+Required scope: `sites:write`
@@ -6241,20 +13925,15 @@ Required scope | `CMS:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
-from webflow.collections.items import ItemsDeleteItemsRequestItemsItem
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.delete_items(
- collection_id="580e63fc8c9a982ac9b8b745",
- items=[
- ItemsDeleteItemsRequestItemsItem(
- id="580e64008c9a982ac9b8b754",
- )
- ],
+client.sites.google_tag.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ tag_id="G-XXXXXXXXXX",
)
```
@@ -6271,7 +13950,7 @@ client.collections.items.delete_items(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6279,7 +13958,7 @@ client.collections.items.delete_items(
-
-**items:** `typing.List[ItemsDeleteItemsRequestItemsItem]`
+**tag_id:** `str` — The Google Tag ID (e.g. G-XXXXXXXXXX)
@@ -6299,7 +13978,8 @@ client.collections.items.delete_items(
-client.collections.items.update_items(...) -> ItemsUpdateItemsResponse
+## Sites Members
+client.sites.members.list_members(...) -> SiteMembersResponse
-
@@ -6311,13 +13991,11 @@ client.collections.items.delete_items(
-
-Update a single item or multiple items in a Collection.
-
-The limit for this endpoint is 100 items.
+List the members of a site and the role each holds.
-Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+This endpoint requires an Enterprise workspace.
-Required scope | `CMS:write`
+Required scope | `site_access:read`
@@ -6332,7 +14010,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItemWithIdInput, CollectionItemWithIdInputFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -6340,43 +14018,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.update_items(
- collection_id="580e63fc8c9a982ac9b8b745",
- skip_invalid_files=True,
- items=[
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5ea6",
- cms_locale_id="66f6e966c9e1dc700a857ca5",
- field_data=CollectionItemWithIdInputFieldData(
- name="Ne Paniquez Pas",
- slug="ne-paniquez-pas",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5ea6",
- cms_locale_id="66f6e966c9e1dc700a857ca4",
- field_data=CollectionItemWithIdInputFieldData(
- name="No Entrar en Pánico",
- slug="no-entrar-en-panico",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5eaa",
- cms_locale_id="66f6e966c9e1dc700a857ca5",
- field_data=CollectionItemWithIdInputFieldData(
- name="Au Revoir et Merci pour Tous les Poissons",
- slug="au-revoir-et-merci",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5eaa",
- cms_locale_id="66f6e966c9e1dc700a857ca4",
- field_data=CollectionItemWithIdInputFieldData(
- name="Hasta Luego y Gracias por Todo el Pescado",
- slug="hasta-luego-y-gracias",
- ),
- )
- ],
+client.sites.members.list_members(
+ site_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ email="email",
+ cursor="cursor",
)
```
@@ -6393,7 +14039,7 @@ client.collections.items.update_items(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6401,7 +14047,7 @@ client.collections.items.update_items(
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+**limit:** `typing.Optional[int]` — Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
@@ -6409,7 +14055,15 @@ client.collections.items.update_items(
-
-**items:** `typing.Optional[typing.List[CollectionItemWithIdInput]]`
+**email:** `typing.Optional[str]` — Filter to the Site Member with this email address
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — Cursor returned in `pagination.nextCursor` of the previous response
@@ -6429,7 +14083,7 @@ client.collections.items.update_items(
-client.collections.items.list_items_live(...) -> CollectionItemList
+client.sites.members.add_member(...) -> SiteMember
-
@@ -6441,13 +14095,13 @@ client.collections.items.update_items(
-
-List all published items in a collection.
+Grant a workspace member access to a site, named by `email` or `memberId`.
-
- Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
-
+This creates access — it does not change an existing grant. A member who already has access to this site is refused with `member_already_has_access`; use `PATCH` to change their Site Role instead.
-Required scope | `CMS:read`
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_access:write`
@@ -6470,15 +14124,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.list_items_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- cms_locale_id="cmsLocaleId",
- offset=1,
- limit=1,
- name="name",
- slug="slug",
- sort_by="lastPublished",
- sort_order="asc",
+client.sites.members.add_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="waylon.dalton@example.com",
+ site_role="designer",
)
```
@@ -6495,7 +14144,7 @@ client.collections.items.list_items_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6503,55 +14152,113 @@ client.collections.items.list_items_live(
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**site_role:** `GrantSiteMemberRequestBodySiteRole` — The Site Role to grant
+
+
+
+
+
+-
+
+**email:** `typing.Optional[str]` — Email address of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+
+
+
+
+-
+
+**member_id:** `typing.Optional[str]` — ID of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+
+
+
+
+-
+
+**site_role_id:** `typing.Optional[str]` — ID of the custom Site Role to grant. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+client.sites.members.remove_member_by_email(...)
+
+-
+
+#### 📝 Description
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
-
-
-
-
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
-
+Remove a member's access to a site, identifying them by email address.
+
+Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+Equivalent to [Revoke Site Access](/data/reference/enterprise/site-access/remove-member), which identifies the member by ID instead.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_access:write`
+
+
+
+#### 🔌 Usage
-
-**name:** `typing.Optional[str]` — Filter by the exact name of the item(s)
-
-
-
-
-
-**slug:** `typing.Optional[str]` — Filter by the exact slug of the item
-
+```python
+from webflow import Webflow
+from webflow.environment import WebflowEnvironment
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.members.remove_member_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+)
+
+```
+
+
+
+#### ⚙️ Parameters
-
-**last_published:** `typing.Optional[ItemsListItemsLiveRequestLastPublished]` — Filter by the last published date of the item(s)
-
-
-
-
-
-**sort_by:** `typing.Optional[ItemsListItemsLiveRequestSortBy]` — Sort results by the provided value
+**site_id:** `str` — Unique identifier for a Site
@@ -6559,7 +14266,7 @@ client.collections.items.list_items_live(
-
-**sort_order:** `typing.Optional[ItemsListItemsLiveRequestSortOrder]` — Sorts the results by asc or desc
+**email:** `str` — Email address of the Site Member to revoke access from
@@ -6579,7 +14286,7 @@ client.collections.items.list_items_live(
-client.collections.items.create_item_live(...) -> CollectionItem
+client.sites.members.update_member_role_by_email(...) -> SiteMember
-
@@ -6591,13 +14298,15 @@ client.collections.items.list_items_live(
-
-Create item(s) in a collection that will be immediately published to the live site.
+Update a site member's role, identifying them by email address.
+On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
-To create items across multiple locales, [please use this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+Equivalent to [Update Site Member Role](/data/reference/enterprise/site-access/update-member-role), which identifies the member by ID instead.
+This endpoint requires an Enterprise workspace.
-Required scope | `CMS:write`
+Required scope | `site_access:write`
@@ -6612,7 +14321,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItem, CollectionItemFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -6620,17 +14329,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.create_item_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- skip_invalid_files=True,
- request=CollectionItem(
- is_archived=False,
- is_draft=False,
- field_data=CollectionItemFieldData(
- name="The Hitchhiker\'s Guide to the Galaxy",
- slug="hitchhikers-guide-to-the-galaxy",
- ),
- ),
+client.sites.members.update_member_role_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+ site_role="content_editor",
)
```
@@ -6647,7 +14349,7 @@ client.collections.items.create_item_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6655,7 +14357,7 @@ client.collections.items.create_item_live(
-
-**request:** `ItemsCreateItemLiveRequestBody`
+**email:** `str` — Email address of the Site Member whose role to change
@@ -6663,7 +14365,7 @@ client.collections.items.create_item_live(
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+**request:** `UpdateSiteMemberRoleRequestBody`
@@ -6683,7 +14385,7 @@ client.collections.items.create_item_live(
-client.collections.items.delete_items_live(...)
+client.sites.members.remove_member(...)
-
@@ -6695,11 +14397,15 @@ client.collections.items.create_item_live(
-
-Unpublish up to 100 items from the live site and set the `isDraft` property to `true`.
+Remove a member's access to a site.
-Items will only be unpublished in the primary locale unless a `cmsLocaleId` is included in the request.
+Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
-Required scope | `CMS:write`
+To revoke by email address instead of by ID, see [Revoke Site Access (by email)](/data/reference/enterprise/site-access/remove-member-by-email).
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_access:write`
@@ -6716,20 +14422,15 @@ Required scope | `CMS:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
-from webflow.collections.items import ItemsDeleteItemsLiveRequestItemsItem
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.delete_items_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- items=[
- ItemsDeleteItemsLiveRequestItemsItem(
- id="580e64008c9a982ac9b8b754",
- )
- ],
+client.sites.members.remove_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
)
```
@@ -6746,7 +14447,7 @@ client.collections.items.delete_items_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6754,7 +14455,7 @@ client.collections.items.delete_items_live(
-
-**items:** `typing.List[ItemsDeleteItemsLiveRequestItemsItem]`
+**member_id:** `str` — Unique identifier for a Member
@@ -6774,7 +14475,7 @@ client.collections.items.delete_items_live(
-client.collections.items.update_items_live(...) -> CollectionItemListNoPagination
+client.sites.members.update_member_role(...) -> SiteMember
-
@@ -6786,11 +14487,15 @@ client.collections.items.delete_items_live(
-
-Update a single published item or multiple published items (up to 100) in a Collection
+Update a site member's role.
-Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
-Required scope | `CMS:write`
+To update by email address instead of by ID, see [Update Site Member Role (by email)](/data/reference/enterprise/site-access/update-member-role-by-email).
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `site_access:write`
@@ -6805,7 +14510,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItemWithIdInput, CollectionItemWithIdInputFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -6813,43 +14518,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.update_items_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- skip_invalid_files=True,
- items=[
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5ea6",
- cms_locale_id="66f6e966c9e1dc700a857ca5",
- field_data=CollectionItemWithIdInputFieldData(
- name="Ne Paniquez Pas",
- slug="ne-paniquez-pas",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5ea6",
- cms_locale_id="66f6e966c9e1dc700a857ca4",
- field_data=CollectionItemWithIdInputFieldData(
- name="No Entrar en Pánico",
- slug="no-entrar-en-panico",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5eaa",
- cms_locale_id="66f6e966c9e1dc700a857ca5",
- field_data=CollectionItemWithIdInputFieldData(
- name="Au Revoir et Merci pour Tous les Poissons",
- slug="au-revoir-et-merci",
- ),
- ),
- CollectionItemWithIdInput(
- id="66f6ed9576ddacf3149d5eaa",
- cms_locale_id="66f6e966c9e1dc700a857ca4",
- field_data=CollectionItemWithIdInputFieldData(
- name="Hasta Luego y Gracias por Todo el Pescado",
- slug="hasta-luego-y-gracias",
- ),
- )
- ],
+client.sites.members.update_member_role(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ site_role="content_editor",
)
```
@@ -6866,7 +14538,7 @@ client.collections.items.update_items_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -6874,7 +14546,7 @@ client.collections.items.update_items_live(
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+**member_id:** `str` — Unique identifier for a Member
@@ -6882,7 +14554,7 @@ client.collections.items.update_items_live(
-
-**items:** `typing.Optional[typing.List[CollectionItemWithIdInput]]`
+**request:** `UpdateSiteMemberRoleRequestBody`
@@ -6902,7 +14574,8 @@ client.collections.items.update_items_live(
-client.collections.items.create_items(...) -> BulkCollectionItem
+## Sites ActivityLogs
+client.sites.activity_logs.list(...) -> SiteActivityLogResponse
-
@@ -6914,14 +14587,11 @@ client.collections.items.update_items_live(
-
-Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+Retrieve Activity Logs for a specific Site.
-
- - This endpoint can create up to 100 items in a request.
- - If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
-
+This endpoint requires an Enterprise workspace.
-Required scope | `CMS:write`
+Required scope: `site_activity:read`
@@ -6938,27 +14608,16 @@ Required scope | `CMS:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
-from webflow.collections.items import SingleCmsItem
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.create_items(
- collection_id="580e63fc8c9a982ac9b8b745",
- skip_invalid_files=True,
- cms_locale_ids=[
- "66f6e966c9e1dc700a857ca3",
- "66f6e966c9e1dc700a857ca4",
- "66f6e966c9e1dc700a857ca5"
- ],
- is_archived=False,
- is_draft=False,
- field_data=SingleCmsItem(
- name="Don’t Panic",
- slug="dont-panic",
- ),
+client.sites.activity_logs.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ offset=1,
)
```
@@ -6975,31 +14634,7 @@ client.collections.items.create_items(
-
-**collection_id:** `str` — Unique identifier for a Collection
-
-
-
-
-
--
-
-**field_data:** `CreateBulkCollectionItemRequestBodyFieldData`
-
-
-
-
-
--
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
-
-
-
-
-
--
-
-**cms_locale_ids:** `typing.Optional[typing.List[str]]` — Array of identifiers for the locales where the item will be created
+**site_id:** `str` — Unique identifier for a Site
@@ -7007,7 +14642,7 @@ client.collections.items.create_items(
-
-**is_archived:** `typing.Optional[bool]` — Indicates whether the item is archived.
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -7015,7 +14650,7 @@ client.collections.items.create_items(
-
-**is_draft:** `typing.Optional[bool]` — Indicates whether the item is in draft state.
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
@@ -7035,7 +14670,8 @@ client.collections.items.create_items(
-client.collections.items.get_item(...) -> CollectionItem
+## Sites Comments
+client.sites.comments.list_comment_threads(...) -> CommentThreadList
-
@@ -7047,9 +14683,13 @@ client.collections.items.create_items(
-
-Get details of a selected Collection Item.
+List all comment threads for a site.
-Required scope | `CMS:read`
+
+ There may be a delay of up to 5 minutes before new comments appear in the system.
+
+
+Required scope | `comments:read`
@@ -7072,10 +14712,13 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.get_item(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- cms_locale_id="cmsLocaleId",
+client.sites.comments.list_comment_threads(
+ site_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
+ offset=1,
+ limit=1,
+ sort_by="createdOn",
+ sort_order="asc",
)
```
@@ -7092,7 +14735,7 @@ client.collections.items.get_item(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -7100,7 +14743,11 @@ client.collections.items.get_item(
-
-**item_id:** `str` — Unique identifier for an Item
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
@@ -7108,7 +14755,31 @@ client.collections.items.get_item(
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[CommentsListCommentThreadsRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+
+
+
+
+
+-
+
+**sort_order:** `typing.Optional[CommentsListCommentThreadsRequestSortOrder]` — Sorts the results by asc or desc
@@ -7128,7 +14799,7 @@ client.collections.items.get_item(
-client.collections.items.delete_item(...)
+client.sites.comments.get_comment_thread(...) -> CommentThread
-
@@ -7140,9 +14811,13 @@ client.collections.items.get_item(
-
-Delete an item from a collection.
+Get details of a specific comment thread.
-Required scope | `CMS:write`
+
+ There may be a delay of up to 5 minutes before new comments appear in the system.
+
+
+Required scope | `comments:read`
@@ -7165,10 +14840,14 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.delete_item(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- cms_locale_id="cmsLocaleId",
+client.sites.comments.get_comment_thread(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
+ offset=1,
+ limit=1,
+ sort_by="createdOn",
+ sort_order="asc",
)
```
@@ -7185,7 +14864,7 @@ client.collections.items.delete_item(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -7193,7 +14872,7 @@ client.collections.items.delete_item(
-
-**item_id:** `str` — Unique identifier for an Item
+**comment_thread_id:** `str` — Unique identifier for a Comment Thread
@@ -7201,7 +14880,43 @@ client.collections.items.delete_item(
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[CommentsGetCommentThreadRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+
+
+
+
+
+-
+
+**sort_order:** `typing.Optional[CommentsGetCommentThreadRequestSortOrder]` — Sorts the results by asc or desc
@@ -7221,7 +14936,7 @@ client.collections.items.delete_item(
-client.collections.items.update_item(...) -> CollectionItem
+client.sites.comments.resolve_comment_thread(...) -> CommentThread
-
@@ -7233,9 +14948,13 @@ client.collections.items.delete_item(
-
-Update a selected Item in a Collection.
+Resolve or unresolve a comment thread.
-Required scope | `CMS:write`
+
+ This endpoint is rate limited to 60 requests per minute per site.
+
+
+Required scope | `comments:write`
@@ -7250,7 +14969,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItemPatchSingleFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -7258,16 +14977,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.update_item(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- skip_invalid_files=True,
- is_archived=False,
- is_draft=False,
- field_data=CollectionItemPatchSingleFieldData(
- name="The Hitchhiker\'s Guide to the Galaxy",
- slug="hitchhikers-guide-to-the-galaxy",
- ),
+client.sites.comments.resolve_comment_thread(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ resolved=True,
)
```
@@ -7284,15 +14997,7 @@ client.collections.items.update_item(
-
-**collection_id:** `str` — Unique identifier for a Collection
-
-
-
-
-
--
-
-**item_id:** `str` — Unique identifier for an Item
+**site_id:** `str` — Unique identifier for a Site
@@ -7300,7 +15005,7 @@ client.collections.items.update_item(
-
-**request:** `CollectionItemPatchSingle`
+**comment_thread_id:** `str` — Unique identifier for a Comment Thread
@@ -7308,7 +15013,7 @@ client.collections.items.update_item(
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+**resolved:** `bool` — Set to `true` to resolve the thread, or `false` to unresolve it
@@ -7328,7 +15033,7 @@ client.collections.items.update_item(
-client.collections.items.get_item_live(...) -> CollectionItem
+client.sites.comments.list_comment_replies(...) -> CommentReplyList
-
@@ -7340,13 +15045,13 @@ client.collections.items.update_item(
-
-Get details of a selected Collection live Item.
+List all replies to a specific comment thread.
-
- Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
-
+
+ There may be a delay of up to 5 minutes before new comments appear in the system.
+
-Required scope | `CMS:read`
+Required scope | `comments:read`
@@ -7369,10 +15074,14 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.get_item_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- cms_locale_id="cmsLocaleId",
+client.sites.comments.list_comment_replies(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
+ offset=1,
+ limit=1,
+ sort_by="createdOn",
+ sort_order="asc",
)
```
@@ -7389,7 +15098,7 @@ client.collections.items.get_item_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -7397,7 +15106,7 @@ client.collections.items.get_item_live(
-
-**item_id:** `str` — Unique identifier for an Item
+**comment_thread_id:** `str` — Unique identifier for a Comment Thread
@@ -7405,7 +15114,43 @@ client.collections.items.get_item_live(
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**locale_id:** `typing.Optional[str]`
+
+Unique identifier for a specific Locale.
+
+[Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
+**sort_by:** `typing.Optional[CommentsListCommentRepliesRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+
+
+
+
+
+-
+
+**sort_order:** `typing.Optional[CommentsListCommentRepliesRequestSortOrder]` — Sorts the results by asc or desc
@@ -7425,7 +15170,7 @@ client.collections.items.get_item_live(
-client.collections.items.delete_item_live(...)
+client.sites.comments.create_comment_reply(...) -> CommentReply
-
@@ -7437,11 +15182,16 @@ client.collections.items.get_item_live(
-
-Unpublish a live item from the site and set the `isDraft` property to `true`.
+Create a reply to an existing comment thread.
-For bulk unpublishing, please use [this endpoint.](/data/v2.0.0/reference/cms/collection-items/live-items/delete-items-live)
+The reply author is always the user who authorized the OAuth token.
+To @mention a user in the reply, include their user ID in double square brackets in the `content` field, as in `[[userId]]`.
-Required scope | `CMS:write`
+
+ The `comment_created` webhook fires automatically when a reply is created.
+
+
+Required scope | `comments:write`
@@ -7464,10 +15214,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.delete_item_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- cms_locale_id="cmsLocaleId",
+client.sites.comments.create_comment_reply(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ content="Thanks for the feedback [[6287ec36a841b25637c663df]]!",
)
```
@@ -7484,7 +15234,7 @@ client.collections.items.delete_item_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -7492,7 +15242,7 @@ client.collections.items.delete_item_live(
-
-**item_id:** `str` — Unique identifier for an Item
+**comment_thread_id:** `str` — Unique identifier for a Comment Thread
@@ -7500,7 +15250,7 @@ client.collections.items.delete_item_live(
-
-**cms_locale_id:** `typing.Optional[str]` — Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+**content:** `str` — The text content of the reply. To @mention a user, include their user ID in double square brackets, as in `[[userId]]`.
@@ -7520,7 +15270,8 @@ client.collections.items.delete_item_live(
-client.collections.items.update_item_live(...) -> CollectionItem
+## Sites Scripts
+client.sites.scripts.get_custom_code(...) -> ScriptApplyList
-
@@ -7532,9 +15283,15 @@ client.collections.items.delete_item_live(
-
-Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
+Get all scripts applied to a site by the App.
-Required scope | `CMS:write`
+
+ To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:read`
@@ -7549,7 +15306,7 @@ Required scope | `CMS:write`
-
```python
-from webflow import Webflow, CollectionItemPatchSingleFieldData
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -7557,16 +15314,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.update_item_live(
- collection_id="580e63fc8c9a982ac9b8b745",
- item_id="580e64008c9a982ac9b8b754",
- skip_invalid_files=True,
- is_archived=False,
- is_draft=False,
- field_data=CollectionItemPatchSingleFieldData(
- name="The Hitchhiker\'s Guide to the Galaxy",
- slug="hitchhikers-guide-to-the-galaxy",
- ),
+client.sites.scripts.get_custom_code(
+ site_id="580e63e98c9a982ac9b8b741",
)
```
@@ -7583,31 +15332,7 @@ client.collections.items.update_item_live(
-
-**collection_id:** `str` — Unique identifier for a Collection
-
-
-
-
-
--
-
-**item_id:** `str` — Unique identifier for an Item
-
-
-
-
-
--
-
-**request:** `CollectionItemPatchSingle`
-
-
-
-
-
--
-
-**skip_invalid_files:** `typing.Optional[bool]` — When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+**site_id:** `str` — Unique identifier for a Site
@@ -7627,7 +15352,7 @@ client.collections.items.update_item_live(
-client.collections.items.publish_item(...) -> ItemsPublishItemResponse
+client.sites.scripts.upsert_custom_code(...) -> ScriptApplyList
-
@@ -7639,9 +15364,15 @@ client.collections.items.update_item_live(
-
-Publish an item or multiple items.
+Apply registered scripts to a site. If you have multiple scripts your App needs to apply or maintain on a site, ensure they are always included in the request body for this endpoint. To remove individual scripts, simply call this endpoint without the script in the request body.
-Required scope | `cms:write`
+
+ To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:write`
@@ -7656,24 +15387,31 @@ Required scope | `cms:write`
-
```python
-from webflow import Webflow
+from webflow import Webflow, ScriptApply
from webflow.environment import WebflowEnvironment
-from webflow.collections.items import ItemIDs
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.collections.items.publish_item(
- collection_id="580e63fc8c9a982ac9b8b745",
- request=ItemIDs(
- item_ids=[
- "643fd856d66b6528195ee2ca",
- "643fd856d66b6528195ee2cb",
- "643fd856d66b6528195ee2cc"
- ],
- ),
+client.sites.scripts.upsert_custom_code(
+ site_id="580e63e98c9a982ac9b8b741",
+ scripts=[
+ ScriptApply(
+ id="cms_slider",
+ location="header",
+ version="1.0.0",
+ attributes={
+ "my-attribute": "some-value"
+ },
+ ),
+ ScriptApply(
+ id="alert",
+ location="header",
+ version="0.0.1",
+ )
+ ],
)
```
@@ -7690,7 +15428,7 @@ client.collections.items.publish_item(
-
-**collection_id:** `str` — Unique identifier for a Collection
+**site_id:** `str` — Unique identifier for a Site
@@ -7698,7 +15436,7 @@ client.collections.items.publish_item(
-
-**request:** `ItemsPublishItemRequest`
+**request:** `ScriptApplyList`
@@ -7718,8 +15456,7 @@ client.collections.items.publish_item(
-## Pages Scripts
-client.pages.scripts.get_custom_code(...) -> ScriptApplyList
+client.sites.scripts.delete_custom_code(...)
-
@@ -7731,9 +15468,13 @@ client.collections.items.publish_item(
-
-Get all scripts applied to a page.
+Remove all scripts from a site applied by the App. This endpoint will not remove scripts from the site's registered scripts.
-Required scope | `custom_code:read`
+To remove individual scripts applied by the App, use the [Add/Update Custom Code](/data/reference/custom-code/custom-code-sites/upsert-custom-code) endpoint.
+
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:write`
@@ -7756,8 +15497,8 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.pages.scripts.get_custom_code(
- page_id="63c720f9347c2139b248e552",
+client.sites.scripts.delete_custom_code(
+ site_id="580e63e98c9a982ac9b8b741",
)
```
@@ -7774,7 +15515,7 @@ client.pages.scripts.get_custom_code(
-
-**page_id:** `str` — Unique identifier for a Page
+**site_id:** `str` — Unique identifier for a Site
@@ -7794,7 +15535,7 @@ client.pages.scripts.get_custom_code(
-client.pages.scripts.upsert_custom_code(...) -> ScriptApplyList
+client.sites.scripts.list_custom_code_blocks(...) -> ListCustomCodeBlocks
-
@@ -7806,13 +15547,17 @@ client.pages.scripts.get_custom_code(
-
-Apply registered scripts to a page. If you have multiple scripts your App needs to apply or maintain on a page, ensure they are always included in the request body for this endpoint. To remove individual scripts, simply call this endpoint without the script in the request body.
+Get a list of scripts that have been applied to a site and/or individual pages.
- To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints.
+
+ See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
-Required scope | `custom_code:write`
+Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+Required scope | `custom_code:read`
@@ -7827,7 +15572,7 @@ Required scope | `custom_code:write`
-
```python
-from webflow import Webflow, ScriptApply
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -7835,23 +15580,10 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.pages.scripts.upsert_custom_code(
- page_id="63c720f9347c2139b248e552",
- scripts=[
- ScriptApply(
- id="cms_slider",
- location="header",
- version="1.0.0",
- attributes={
- "my-attribute": "some-value"
- },
- ),
- ScriptApply(
- id="alert",
- location="header",
- version="0.0.1",
- )
- ],
+client.sites.scripts.list_custom_code_blocks(
+ site_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
)
```
@@ -7868,7 +15600,7 @@ client.pages.scripts.upsert_custom_code(
-
-**page_id:** `str` — Unique identifier for a Page
+**site_id:** `str` — Unique identifier for a Site
@@ -7876,7 +15608,15 @@ client.pages.scripts.upsert_custom_code(
-
-**request:** `ScriptApplyList`
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -7896,7 +15636,8 @@ client.pages.scripts.upsert_custom_code(
-client.pages.scripts.delete_custom_code(...)
+## Sites Forms
+client.sites.forms.list_submissions_by_site(...) -> FormSubmissionList
-
@@ -7908,13 +15649,17 @@ client.pages.scripts.upsert_custom_code(
-
-Remove all scripts from a page applied by the App. This endpoint will not remove scripts from the site's registered scripts.
+List all form submissions for a given site with the ability to filter submissions by a centralized `elementId`.
-To remove individual scripts applied by the App, use the [Add/Update Custom Code](/data/reference/custom-code/custom-code-pages/upsert-custom-code) endpoint.
+Add `elementId` when you want to filter form submissions to a specific form in a site. You can get the `elementId` from the [List forms endpoint](/data/reference/forms/forms/list) (displayed as `formElementId` in the response).
-Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
+When a form is used in a Webflow component definition, each instance of the component will yield a unique form. Adding the `elementId` in this request ensures this API response includes all submissions from that core form, wherever that form is used in instantiated components.
+
-Required scope | `custom_code:write`
+Use the [List Form Submissions endpoint](/data/reference/forms/form-submissions/list-submissions) to list form submissions for a given form ID.
+
+Required scope | `forms:read`
@@ -7937,8 +15682,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.pages.scripts.delete_custom_code(
- page_id="63c720f9347c2139b248e552",
+client.sites.forms.list_submissions_by_site(
+ site_id="580e63e98c9a982ac9b8b741",
+ element_id="18259716-3e5a-646a-5f41-5dc4b9405aa0",
+ offset=1,
+ limit=1,
)
```
@@ -7955,7 +15703,31 @@ client.pages.scripts.delete_custom_code(
-
-**page_id:** `str` — Unique identifier for a Page
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**element_id:** `typing.Optional[str]` — Identifier for an element
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -7975,8 +15747,7 @@ client.pages.scripts.delete_custom_code(
-## Sites Redirects
-client.sites.redirects.list(...) -> Redirects
+client.sites.forms.list_submissions(...) -> FormSubmissionList
-
@@ -7988,13 +15759,11 @@ client.pages.scripts.delete_custom_code(
-
-Fetch a list of all 301 redirect rules configured for a specific site.
-
-Use this endpoint to review, audit, or manage the redirection rules that control how traffic is rerouted on your site.
+List form submissions for a given form ID within a specific site.
-This endpoint requires an Enterprise workspace.
+Use the [List Form Submissions by Site endpoint](/data/reference/forms/form-submissions/list-submissions-by-site) to list form submissions for a given site with the ability to filter by a `formElementId`.
-Required scope: `sites:read`
+Required scope | `forms:read`
@@ -8017,8 +15786,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.redirects.list(
+client.sites.forms.list_submissions(
site_id="580e63e98c9a982ac9b8b741",
+ form_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
)
```
@@ -8043,6 +15815,30 @@ client.sites.redirects.list(
-
+**form_id:** `str` — Unique identifier for a Form
+
+
+
+
+
+-
+
+**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -8055,7 +15851,7 @@ client.sites.redirects.list(
-client.sites.redirects.create(...) -> Redirect
+client.sites.forms.get_submission(...) -> FormSubmission
-
@@ -8067,13 +15863,9 @@ client.sites.redirects.list(
-
-Add a new 301 redirection rule to a site.
-
-This endpoint allows you to define a source path (`fromUrl`) and its corresponding destination path (`toUrl`), which will dictate how traffic is rerouted on your site. This is useful for managing site changes, restructuring URLs, or handling outdated links.
-
-This endpoint requires an Enterprise workspace.
+Get information about a form submission within a specific site.
-Required scope: `sites:write`
+Required scope | `forms:read`
@@ -8096,11 +15888,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.redirects.create(
+client.sites.forms.get_submission(
site_id="580e63e98c9a982ac9b8b741",
- id="42e1a2b7aa1a13f768a0042a",
- from_url="/mostly-harmless",
- to_url="/earth",
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -8125,7 +15915,7 @@ client.sites.redirects.create(
-
-**request:** `Redirect`
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -8145,7 +15935,7 @@ client.sites.redirects.create(
-client.sites.redirects.delete(...) -> Redirects
+client.sites.forms.delete_submission(...)
-
@@ -8157,13 +15947,9 @@ client.sites.redirects.create(
-
-Remove a 301 redirection rule from a site.
-
-This is useful for cleaning up outdated or unnecessary redirects, ensuring that your site's routing behavior remains efficient and up-to-date.
-
-This endpoint requires an Enterprise workspace.
+Delete a form submission within a specific site.
-Required scope: `sites:write`
+Required scope | `forms:write`
@@ -8186,9 +15972,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.redirects.delete(
+client.sites.forms.delete_submission(
site_id="580e63e98c9a982ac9b8b741",
- redirect_id="66c4cb9a20cac35ed19500e6",
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -8213,7 +15999,7 @@ client.sites.redirects.delete(
-
-**redirect_id:** `str` — Unique identifier site redirect
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -8233,7 +16019,7 @@ client.sites.redirects.delete(
-client.sites.redirects.update(...) -> Redirect
+client.sites.forms.update_submission(...) -> FormSubmission
-
@@ -8245,11 +16031,9 @@ client.sites.redirects.delete(
-
-Update a 301 redirection rule from a site.
-
-This endpoint requires an Enterprise workspace.
+Update hidden fields on a form submission within a specific site.
-Required scope: `sites:write`
+Required scope | `forms:write`
@@ -8272,12 +16056,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.redirects.update(
+client.sites.forms.update_submission(
site_id="580e63e98c9a982ac9b8b741",
- redirect_id="66c4cb9a20cac35ed19500e6",
- id="42e1a2b7aa1a13f768a0042a",
- from_url="/mostly-harmless",
- to_url="/earth",
+ form_submission_id="580e63e98c9a982ac9b8b741",
)
```
@@ -8302,7 +16083,7 @@ client.sites.redirects.update(
-
-**redirect_id:** `str` — Unique identifier site redirect
+**form_submission_id:** `str` — Unique identifier for a Form Submission
@@ -8310,7 +16091,7 @@ client.sites.redirects.update(
-
-**request:** `Redirect`
+**form_submission_data:** `typing.Optional[typing.Dict[str, typing.Any]]` — An existing **hidden field** defined on the form schema, and the corresponding value to set
@@ -8330,8 +16111,8 @@ client.sites.redirects.update(
-## Sites Plans
-client.sites.plans.get_site_plan(...) -> SitePlan
+## Sites Aeo Reports
+client.sites.aeo.reports.bot_traffic(...) -> AeoBotTrafficResponse
-
@@ -8343,9 +16124,13 @@ client.sites.redirects.update(
-
-Get site plan details for the specified Site.
+Returns the total number of requests made to the site by the AI bots Webflow tracks, over a time range. This counts bot requests, not visitors or page views. For visitor traffic, use the [Analyze API](/data/docs/working-with-analyze). Filter by bot provider or bot category.
-This endpoint requires an Enterprise workspace.
+Counts successful requests (HTTP `200` or `304`). Bot providers combine with OR, and `botCategory` narrows them further.
+
+This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
Required scope | `sites:read`
@@ -8364,31 +16149,70 @@ Required scope | `sites:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.plans.get_site_plan(
+client.sites.aeo.reports.bot_traffic(
site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ bot_providers=[
+ "openai"
+ ],
+ bot_category="agent",
)
-```
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**site_id:** `str` — Unique identifier for a Site
+
+
+
+
+
+-
+
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
-#### ⚙️ Parameters
-
-
+**bot_providers:** `typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]` — Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+
+
+
-
-**site_id:** `str` — Unique identifier for a Site
+**bot_category:** `typing.Optional[AeoBotCategory]` — Count only bots in this category.
@@ -8408,8 +16232,7 @@ client.sites.plans.get_site_plan(
-## Sites RobotsTxt
-client.sites.robots_txt.get(...) -> Robots
+client.sites.aeo.reports.bot_traffic_pages(...) -> AeoBotTrafficPagesResponse
-
@@ -8421,11 +16244,15 @@ client.sites.plans.get_site_plan(
-
-Retrieve the robots.txt configuration for various user agents.
+Returns the pages and files that the AI bots Webflow tracks requested most over a time range, with each one's request count.
-This endpoint requires an Enterprise workspace.
+Results are sorted by `requestCount`, highest first, and aren't paginated. Each row is one combination of page, CMS item, and request path, so a page can appear more than once.
-Required scope: `site_config:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -8442,14 +16269,22 @@ Required scope: `site_config:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.robots_txt.get(
+client.sites.aeo.reports.bot_traffic_pages(
site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ bot_providers=[
+ "openai"
+ ],
+ bot_category="agent",
+ limit=1,
)
```
@@ -8474,6 +16309,46 @@ client.sites.robots_txt.get(
-
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+
+
+
+
+-
+
+**bot_providers:** `typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]` — Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+
+
+
+
+-
+
+**bot_category:** `typing.Optional[AeoBotCategory]` — Count only bots in this category.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of rows to return. Defaults to `100`, which is also the maximum.
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -8486,7 +16361,7 @@ client.sites.robots_txt.get(
-client.sites.robots_txt.put(...) -> Robots
+client.sites.aeo.reports.visibility(...) -> AeoVisibilityResponse
-
@@ -8498,11 +16373,15 @@ client.sites.robots_txt.get(
-
-Replace the `robots.txt` configuration for various user agents.
+Returns the share of answer engine responses that mention your organization, for a time range or as a daily or weekly series.
-This endpoint requires an Enterprise workspace.
+Without `timeseries`, returns one score for the whole time range. With `timeseries`, returns one score per day or week. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
-Required scope | `site_config:write`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -8517,29 +16396,26 @@ Required scope | `site_config:write`
-
```python
-from webflow import Webflow, RobotsRulesItem
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.robots_txt.put(
+client.sites.aeo.reports.visibility(
site_id="580e63e98c9a982ac9b8b741",
- rules=[
- RobotsRulesItem(
- user_agent="googlebot",
- allows=[
- "/public"
- ],
- disallows=[
- "/vogon-poetry",
- "/total-perspective-vortex"
- ],
- )
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
],
- sitemap="https://heartofgold.ship/sitemap.xml",
+ prompt_ids=[
+ "68cdb4b5a699862fcd8c1a01"
+ ],
+ answer_engine="chat-gpt",
)
```
@@ -8564,7 +16440,47 @@ client.sites.robots_txt.put(
-
-**request:** `Robots`
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+
+
+
+
+-
+
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+
+
+
+
+-
+
+**prompt_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+
+
+
+
+-
+
+**answer_engine:** `typing.Optional[AeoAnswerEngineKey]` — Include only responses from this answer engine.
+
+
+
+
+
+-
+
+**timeseries:** `typing.Optional[AeoTimeseries]` — Return one score per day or week. Send both fields in bracket notation, such as `timeseries[granularityPeriod]=day×eries[bucketTimeZone]=America/New_York`.
@@ -8584,7 +16500,7 @@ client.sites.robots_txt.put(
-client.sites.robots_txt.delete(...) -> Robots
+client.sites.aeo.reports.visibility_breakdown(...) -> AeoVisibilityBreakdownResponse
-
@@ -8596,13 +16512,15 @@ client.sites.robots_txt.put(
-
-Remove specific rules for a user-agent in your `robots.txt` file. To delete all rules for a user-agent, provide an empty rule set. This will remove the user-agent's entry entirely, leaving it subject to your site's default crawling behavior.
+Returns the visibility score for each topic or prompt over a time range, highest first.
-**Note:** Deleting a user-agent with no rules will make the user-agent's access unrestricted unless other directives apply.
+Choose `breakdown=topic` or `breakdown=prompt`. A topic or prompt appears only if it has responses in the time range. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
-This endpoint requires an Enterprise workspace.
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
-Required scope: `site_config:write`
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -8617,27 +16535,28 @@ Required scope: `site_config:write`
-
```python
-from webflow import Webflow, RobotsRulesItem
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.robots_txt.delete(
+client.sites.aeo.reports.visibility_breakdown(
site_id="580e63e98c9a982ac9b8b741",
- rules=[
- RobotsRulesItem(
- user_agent="*",
- allows=[
- "/public"
- ],
- disallows=[
- "/bubbles"
- ],
- )
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ breakdown="topic",
+ limit=1,
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
],
+ prompt_ids=[
+ "68cdb4b5a699862fcd8c1a01"
+ ],
+ answer_engine="chat-gpt",
)
```
@@ -8662,7 +16581,7 @@ client.sites.robots_txt.delete(
-
-**request:** `Robots`
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
@@ -8670,89 +16589,39 @@ client.sites.robots_txt.delete(
-
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
-
-
-
-
-
-
-
-client.sites.robots_txt.patch(...) -> Robots
-
-#### 📝 Description
-
-
--
+**breakdown:** `ReportsVisibilityBreakdownRequestBreakdown` — Return one row per `topic` or per `prompt`.
+
+
+
-
-Update the `robots.txt` configuration for various user agents.
-
-This endpoint requires an Enterprise workspace.
-
-Required scope | `site_config:write`
-
-
+**limit:** `typing.Optional[int]` — Maximum number of rows to return, up to `100`. Omit it to return every row.
+
-#### 🔌 Usage
-
-
-
--
-
-```python
-from webflow import Webflow, RobotsRulesItem
-from webflow.environment import WebflowEnvironment
-
-client = Webflow(
- access_token="",
- environment=WebflowEnvironment.DATA_API,
-)
-
-client.sites.robots_txt.patch(
- site_id="580e63e98c9a982ac9b8b741",
- rules=[
- RobotsRulesItem(
- user_agent="googlebot",
- allows=[
- "/public"
- ],
- disallows=[
- "/vogon-poetry",
- "/total-perspective-vortex"
- ],
- )
- ],
- sitemap="https://heartofgold.ship/sitemap.xml",
-)
-
-```
-
-
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
-#### ⚙️ Parameters
-
-
--
-
-
-**site_id:** `str` — Unique identifier for a Site
+**prompt_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
@@ -8760,7 +16629,7 @@ client.sites.robots_txt.patch(
-
-**request:** `Robots`
+**answer_engine:** `typing.Optional[AeoAnswerEngineKey]` — Include only responses from this answer engine.
@@ -8780,8 +16649,7 @@ client.sites.robots_txt.patch(
-## Sites WellKnown
-client.sites.well_known.put(...)
+client.sites.aeo.reports.citation_rate(...) -> AeoCitationRateResponse
-
@@ -8793,20 +16661,15 @@ client.sites.robots_txt.patch(
-
-Upload a supported well-known file to a site.
+Returns, for each topic or prompt, the share of responses with citations that cited one of your domains.
-The current restrictions on well-known files are as follows:
- - Each file must be smaller than 100kb
- - Less than 30 total files
- - Have one of the following file extensions (or no extension): `.txt`, `.json`, `.noext`
+Your domains are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`. The response lists them in `domains`. Results are sorted by `citationRate`, highest first, and aren't paginated. Separate filters combine with AND, and values within one filter combine with OR.
-
- `.noext` is a special file extension that removes other extensions. For example, `apple-app-site-association.noext.txt` will be uploaded as `apple-app-site-association`. Use this extension for tools that have trouble uploading extensionless files.
-
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
-This endpoint requires an Enterprise workspace.
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
-Required scope: `site_config:write`
+Required scope | `sites:read`
@@ -8823,17 +16686,27 @@ Required scope: `site_config:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.well_known.put(
+client.sites.aeo.reports.citation_rate(
site_id="580e63e98c9a982ac9b8b741",
- file_name="apple-app-site-association.txt",
- file_data="{\n \"applinks\": {\n \"apps\": [],\n \"details\": [\n {\n \"appID\": \"ABCDE12345.com.example.app\",\n \"paths\": [ \"/*\", \"/some/path/*\" ]\n }\n ]\n }\n}\n",
- content_type="application/json",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ breakdown="topic",
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
+ ],
+ prompt_ids=[
+ "68cdb4b5a699862fcd8c1a01"
+ ],
+ answer_engines=[
+ "chat-gpt"
+ ],
)
```
@@ -8858,7 +16731,7 @@ client.sites.well_known.put(
-
-**file_name:** `str` — The name of the file
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
@@ -8866,7 +16739,31 @@ client.sites.well_known.put(
-
-**file_data:** `str` — The contents of the file
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+
+
+
+
+-
+
+**breakdown:** `ReportsCitationRateRequestBreakdown` — Return one row per `topic` or per `prompt`.
+
+
+
+
+
+-
+
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+
+
+
+
+-
+
+**prompt_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
@@ -8874,7 +16771,7 @@ client.sites.well_known.put(
-
-**content_type:** `typing.Optional[WellKnownFileContentType]` — The content type of the file. Defaults to application/json
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
@@ -8894,7 +16791,7 @@ client.sites.well_known.put(
-client.sites.well_known.delete(...)
+client.sites.aeo.reports.top_cited_domains(...) -> AeoTopCitedDomainsResponse
-
@@ -8906,11 +16803,15 @@ client.sites.well_known.put(
-
-Delete existing well-known files from a site.
+Returns the domains answer engines cited most, with each domain's share of all citations. For your domains, each row also lists its cited paths in `paths`. For other domains, `distinctPathCount` gives the approximate number of distinct cited paths.
-This endpoint requires an Enterprise workspace.
+Results are sorted by citation count, highest first. If one of your domains isn't in the top `limit`, it's added after the ranked rows, so the response can have more than `limit` rows.
-Required scope: `site_config:write`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -8927,14 +16828,24 @@ Required scope: `site_config:write`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.well_known.delete(
+client.sites.aeo.reports.top_cited_domains(
site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
+ ],
+ answer_engines=[
+ "chat-gpt"
+ ],
+ limit=1,
)
```
@@ -8959,7 +16870,39 @@ client.sites.well_known.delete(
-
-**file_names:** `typing.Optional[typing.List[str]]` — A list of file names to delete
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
+
+
+-
+
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+
+
+
+
+-
+
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+
+
+
+
+-
+
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Number of top domains to return. Defaults to `5`, up to a maximum of `100`.
@@ -8979,8 +16922,7 @@ client.sites.well_known.delete(
-## Sites ActivityLogs
-client.sites.activity_logs.list(...) -> SiteActivityLogResponse
+client.sites.aeo.reports.cited_domain_paths(...) -> AeoCitedDomainPathsResponse
-
@@ -8992,11 +16934,15 @@ client.sites.well_known.delete(
-
-Retrieve Activity Logs for a specific Site.
+Returns the cited paths on one domain, with each path's share of all citations.
-This endpoint requires an Enterprise workspace.
+`domain` can be any domain, not only yours. Results are sorted by `citationRate`, highest first, and aren't paginated.
-Required scope: `site_activity:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9013,16 +16959,24 @@ Required scope: `site_activity:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.activity_logs.list(
+client.sites.aeo.reports.cited_domain_paths(
site_id="580e63e98c9a982ac9b8b741",
- limit=1,
- offset=1,
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ domain="example.org",
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
+ ],
+ answer_engines=[
+ "chat-gpt"
+ ],
)
```
@@ -9047,7 +17001,7 @@ client.sites.activity_logs.list(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
@@ -9055,7 +17009,31 @@ client.sites.activity_logs.list(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+
+
+
+
+-
+
+**domain:** `str` — The domain, as a bare hostname such as `example.com`, with no scheme, path, or port. Matched exactly, so `www.example.com` is a different domain. Send internationalized domains in punycode.
+
+
+
+
+
+-
+
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+
+
+
+
+-
+
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
@@ -9075,8 +17053,7 @@ client.sites.activity_logs.list(
-## Sites Comments
-client.sites.comments.list_comment_threads(...) -> CommentThreadList
+client.sites.aeo.reports.page_citation_share(...) -> AeoPageCitationShareResponse
-
@@ -9088,13 +17065,15 @@ client.sites.activity_logs.list(
-
-List all comment threads for a site.
+Returns one page's share of all citations to your domains, optionally broken down by prompt.
-
- There may be a delay of up to 5 minutes before new comments appear in the system.
-
+For a CMS item, send `collectionId` and `itemSlug` along with `pageId`. With `breakdown=prompt`, returns each prompt's share of this page's citations, sorted by `citationShare`, highest first.
-Required scope | `comments:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9111,19 +17090,24 @@ Required scope | `comments:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.comments.list_comment_threads(
+client.sites.aeo.reports.page_citation_share(
site_id="580e63e98c9a982ac9b8b741",
- locale_id="65427cf400e02b306eaa04a0",
- offset=1,
- limit=1,
- sort_by="createdOn",
- sort_order="asc",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ page_id="68cdb4b5a699862fcd8c1a30",
+ collection_id="68cdb4b5a699862fcd8c1a40",
+ item_slug="dont-panic",
+ breakdown="prompt",
+ answer_engines=[
+ "chat-gpt"
+ ],
)
```
@@ -9148,11 +17132,15 @@ client.sites.comments.list_comment_threads(
-
-**locale_id:** `typing.Optional[str]`
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+
+
-Unique identifier for a specific Locale.
+
+-
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
@@ -9160,7 +17148,7 @@ Unique identifier for a specific Locale.
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**page_id:** `str` — The page ID. For a CMS item, the ID of its template page.
@@ -9168,7 +17156,7 @@ Unique identifier for a specific Locale.
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**collection_id:** `typing.Optional[str]` — The CMS collection ID, for a CMS item. Send it with `itemSlug`.
@@ -9176,7 +17164,7 @@ Unique identifier for a specific Locale.
-
-**sort_by:** `typing.Optional[CommentsListCommentThreadsRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+**item_slug:** `typing.Optional[str]` — The CMS item's slug, for a CMS item. Send it with `collectionId`.
@@ -9184,7 +17172,15 @@ Unique identifier for a specific Locale.
-
-**sort_order:** `typing.Optional[CommentsListCommentThreadsRequestSortOrder]` — Sorts the results by asc or desc
+**breakdown:** `typing.Optional[typing.Literal]` — Set to `prompt` to return one row per prompt that cited the page.
+
+
+
+
+
+-
+
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
@@ -9204,7 +17200,7 @@ Unique identifier for a specific Locale.
-client.sites.comments.get_comment_thread(...) -> CommentThread
+client.sites.aeo.reports.competitor_citations(...) -> AeoCompetitorCitationsResponse
-
@@ -9216,13 +17212,15 @@ Unique identifier for a specific Locale.
-
-Get details of a specific comment thread.
+Lists the newest responses that cited a competitor's domain and none of your domains.
-
- There may be a delay of up to 5 minutes before new comments appear in the system.
-
+Competitor domains come from the competitors in the site's Organization info. Results are sorted by `timestamp`, newest first, and aren't paginated.
-Required scope | `comments:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9239,20 +17237,24 @@ Required scope | `comments:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.comments.get_comment_thread(
+client.sites.aeo.reports.competitor_citations(
site_id="580e63e98c9a982ac9b8b741",
- comment_thread_id="580e63e98c9a982ac9b8b741",
- locale_id="65427cf400e02b306eaa04a0",
- offset=1,
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
+ ],
+ answer_engines=[
+ "chat-gpt"
+ ],
limit=1,
- sort_by="createdOn",
- sort_order="asc",
)
```
@@ -9277,19 +17279,7 @@ client.sites.comments.get_comment_thread(
-
-**comment_thread_id:** `str` — Unique identifier for a Comment Thread
-
-
-
-
-
--
-
-**locale_id:** `typing.Optional[str]`
-
-Unique identifier for a specific Locale.
-
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
@@ -9297,7 +17287,7 @@ Unique identifier for a specific Locale.
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
@@ -9305,7 +17295,7 @@ Unique identifier for a specific Locale.
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
@@ -9313,7 +17303,7 @@ Unique identifier for a specific Locale.
-
-**sort_by:** `typing.Optional[CommentsGetCommentThreadRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
@@ -9321,7 +17311,7 @@ Unique identifier for a specific Locale.
-
-**sort_order:** `typing.Optional[CommentsGetCommentThreadRequestSortOrder]` — Sorts the results by asc or desc
+**limit:** `typing.Optional[int]` — Maximum number of responses to return. Defaults to `10`, up to a maximum of `1000`.
@@ -9341,7 +17331,7 @@ Unique identifier for a specific Locale.
-client.sites.comments.list_comment_replies(...) -> CommentReplyList
+client.sites.aeo.reports.citations(...) -> AeoCitationsResponse
-
@@ -9353,13 +17343,15 @@ Unique identifier for a specific Locale.
-
-List all replies to a specific comment thread.
+Lists answer engine responses over a time range, newest first, with every citation and mention in each.
-
- There may be a delay of up to 5 minutes before new comments appear in the system.
-
+Includes responses that cited nothing. To get the next page, set `offset` to the previous `offset` plus `limit`. Rows can shift between pages if new responses arrive in the time range.
-Required scope | `comments:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9376,20 +17368,28 @@ Required scope | `comments:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.comments.list_comment_replies(
+client.sites.aeo.reports.citations(
site_id="580e63e98c9a982ac9b8b741",
- comment_thread_id="580e63e98c9a982ac9b8b741",
- locale_id="65427cf400e02b306eaa04a0",
- offset=1,
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-09-08T00:00:00+00:00"),
+ topic_ids=[
+ "68cdb4b5a699862fcd8c1a02"
+ ],
+ prompt_ids=[
+ "68cdb4b5a699862fcd8c1a01"
+ ],
+ answer_engines=[
+ "chat-gpt"
+ ],
limit=1,
- sort_by="createdOn",
- sort_order="asc",
+ offset=1,
)
```
@@ -9414,7 +17414,7 @@ client.sites.comments.list_comment_replies(
-
-**comment_thread_id:** `str` — Unique identifier for a Comment Thread
+**start_time:** `datetime.datetime` — Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
@@ -9422,11 +17422,15 @@ client.sites.comments.list_comment_replies(
-
-**locale_id:** `typing.Optional[str]`
+**end_time:** `datetime.datetime` — Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+
+
-Unique identifier for a specific Locale.
+
+-
-[Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+**topic_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
@@ -9434,7 +17438,7 @@ Unique identifier for a specific Locale.
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**prompt_ids:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
@@ -9442,7 +17446,7 @@ Unique identifier for a specific Locale.
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**answer_engines:** `typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]` — Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
@@ -9450,7 +17454,7 @@ Unique identifier for a specific Locale.
-
-**sort_by:** `typing.Optional[CommentsListCommentRepliesRequestSortBy]` — Sort results by the provided value. Only allowed when sortOrder is provided.
+**limit:** `typing.Optional[int]` — Maximum number of responses to return. Defaults to `100`, up to a maximum of `1000`.
@@ -9458,7 +17462,7 @@ Unique identifier for a specific Locale.
-
-**sort_order:** `typing.Optional[CommentsListCommentRepliesRequestSortOrder]` — Sorts the results by asc or desc
+**offset:** `typing.Optional[int]` — Number of responses to skip. Defaults to `0`.
@@ -9478,8 +17482,8 @@ Unique identifier for a specific Locale.
-## Sites Scripts
-client.sites.scripts.get_custom_code(...) -> ScriptApplyList
+## Sites Aeo Prompts
+client.sites.aeo.prompts.list(...) -> AeoPromptListResponse
-
@@ -9491,13 +17495,15 @@ Unique identifier for a specific Locale.
-
-Get all scripts applied to a site by the App.
+Lists the site's tracked prompts, with each prompt's topic and status. By default, returns `active`, `paused`, and `pending` prompts. To include archived prompts, set `status` to `archived` or `all`.
-
- To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
-
+Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
-Required scope | `custom_code:read`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9520,8 +17526,12 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.scripts.get_custom_code(
+client.sites.aeo.prompts.list(
site_id="580e63e98c9a982ac9b8b741",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ status="active",
+ limit=1,
+ cursor="cursor",
)
```
@@ -9546,6 +17556,38 @@ client.sites.scripts.get_custom_code(
-
+**topic_id:** `typing.Optional[str]` — Include only prompts in this topic. A topic that isn't on this site returns no prompts.
+
+
+
+
+
+-
+
+**status:** `typing.Optional[PromptsListRequestStatus]` — Include only prompts with this status. `all` includes every status. Omit it to include `active`, `paused`, and `pending` prompts.
+
+
+
+
+
+-
+
+**limit:** `typing.Optional[int]` — Maximum number of prompts to return. Defaults to `50`, up to a maximum of `100`.
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — The `nextCursor` from the previous page.
+
+
+
+
+
+-
+
**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
@@ -9558,7 +17600,8 @@ client.sites.scripts.get_custom_code(
-client.sites.scripts.upsert_custom_code(...) -> ScriptApplyList
+## Sites Aeo PromptExecutions
+client.sites.aeo.prompt_executions.list(...) -> AeoPromptExecutionListResponse
-
@@ -9570,13 +17613,15 @@ client.sites.scripts.get_custom_code(
-
-Apply registered scripts to a site. If you have multiple scripts your App needs to apply or maintain on a site, ensure they are always included in the request body for this endpoint. To remove individual scripts, simply call this endpoint without the script in the request body.
+Lists answer engine responses to the site's prompts, newest first, with the mentions and citations in each. Covers the last 90 days unless you set a time range.
-
- To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
-
+Executions older than 90 days aren't listed. Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
-Required scope | `custom_code:write`
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9591,31 +17636,25 @@ Required scope | `custom_code:write`
-
```python
-from webflow import Webflow, ScriptApply
+from webflow import Webflow
from webflow.environment import WebflowEnvironment
-
-client = Webflow(
- access_token="",
- environment=WebflowEnvironment.DATA_API,
-)
-
-client.sites.scripts.upsert_custom_code(
- site_id="580e63e98c9a982ac9b8b741",
- scripts=[
- ScriptApply(
- id="cms_slider",
- location="header",
- version="1.0.0",
- attributes={
- "my-attribute": "some-value"
- },
- ),
- ScriptApply(
- id="alert",
- location="header",
- version="0.0.1",
- )
- ],
+import datetime
+
+client = Webflow(
+ access_token="",
+ environment=WebflowEnvironment.DATA_API,
+)
+
+client.sites.aeo.prompt_executions.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ prompt_id="68cdb4b5a699862fcd8c1a01",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ answer_engine="chat-gpt",
+ start_time=datetime.datetime.fromisoformat("2026-09-01T00:00:00+00:00"),
+ end_time=datetime.datetime.fromisoformat("2026-10-01T00:00:00+00:00"),
+ include_raw_response=True,
+ limit=1,
+ cursor="cursor",
)
```
@@ -9640,7 +17679,7 @@ client.sites.scripts.upsert_custom_code(
-
-**request:** `ScriptApplyList`
+**prompt_id:** `typing.Optional[str]` — Include only executions of this prompt. Can't be sent with `topicId`.
@@ -9648,78 +17687,55 @@ client.sites.scripts.upsert_custom_code(
-
-**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+**topic_id:** `typing.Optional[str]` — Include only executions of prompts in this topic. Can't be sent with `promptId`.
-
-
+
+-
+**answer_engine:** `typing.Optional[AeoAnswerEngineKey]` — Include only responses from this answer engine.
+
-
-client.sites.scripts.delete_custom_code(...)
-
-#### 📝 Description
-
-
--
+**start_time:** `typing.Optional[datetime.datetime]` — Include only executions created at or after this time. Must be a UTC timestamp ending in `Z`. Defaults to 90 days before `endTime`.
+
+
+
-
-Remove all scripts from a site applied by the App. This endpoint will not remove scripts from the site's registered scripts.
-
-To remove individual scripts applied by the App, use the [Add/Update Custom Code](/data/reference/custom-code/custom-code-sites/upsert-custom-code) endpoint.
-
-Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
-
-Required scope | `custom_code:write`
-
-
+**end_time:** `typing.Optional[datetime.datetime]` — Include only executions created before this time. Must be a UTC timestamp ending in `Z`. Defaults to now. The range can be at most 90 days.
+
-#### 🔌 Usage
-
-
-
--
-
-```python
-from webflow import Webflow
-from webflow.environment import WebflowEnvironment
-
-client = Webflow(
- access_token="",
- environment=WebflowEnvironment.DATA_API,
-)
-
-client.sites.scripts.delete_custom_code(
- site_id="580e63e98c9a982ac9b8b741",
-)
-
-```
-
-
+**include_raw_response:** `typing.Optional[bool]` — Set to `true` to include each execution's full response text in `rawResponse`.
+
-#### ⚙️ Parameters
-
-
+**limit:** `typing.Optional[int]` — Maximum number of executions to return. Defaults to `25`, which is also the maximum.
+
+
+
+
-
-**site_id:** `str` — Unique identifier for a Site
+**cursor:** `typing.Optional[str]` — The `nextCursor` from the previous page.
@@ -9739,7 +17755,7 @@ client.sites.scripts.delete_custom_code(
-client.sites.scripts.list_custom_code_blocks(...) -> ListCustomCodeBlocks
+client.sites.aeo.prompt_executions.get(...) -> AeoPromptExecution
-
@@ -9751,15 +17767,15 @@ client.sites.scripts.delete_custom_code(
-
-Get a list of scripts that have been applied to a site and/or individual pages.
+Returns one prompt execution, including the full response text while it's still stored.
-
- To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints.
+Executions older than 90 days are returned without `rawResponse`. Executions are deleted 120 days after they're created. This endpoint takes no query parameters.
- See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
-
+This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
-Required scope | `custom_code:read`
+Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+Required scope | `sites:read`
@@ -9782,10 +17798,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.scripts.list_custom_code_blocks(
+client.sites.aeo.prompt_executions.get(
site_id="580e63e98c9a982ac9b8b741",
- offset=1,
- limit=1,
+ execution_id="68cdb4b5a699862fcd8c1a10",
)
```
@@ -9810,15 +17825,7 @@ client.sites.scripts.list_custom_code_blocks(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
-
-
-
-
-
--
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**execution_id:** `str` — The prompt execution ID, from [List Prompt Executions](/data/reference/aeo-analytics/list-prompt-executions).
@@ -9838,8 +17845,8 @@ client.sites.scripts.list_custom_code_blocks(
-## Sites Forms
-client.sites.forms.list_submissions_by_site(...) -> FormSubmissionList
+## Workspaces AuditLogs
+client.workspaces.audit_logs.get_workspace_audit_logs(...) -> WorkspaceAuditLogResponse
-
@@ -9851,17 +17858,11 @@ client.sites.scripts.list_custom_code_blocks(
-
-List all form submissions for a given site with the ability to filter submissions by a centralized `elementId`.
-
-Add `elementId` when you want to filter form submissions to a specific form in a site. You can get the `elementId` from the [List forms endpoint](/data/reference/forms/forms/list) (displayed as `formElementId` in the response).
-
-
-When a form is used in a Webflow component definition, each instance of the component will yield a unique form. Adding the `elementId` in this request ensures this API response includes all submissions from that core form, wherever that form is used in instantiated components.
-
+Get audit logs for a workspace.
-Use the [List Form Submissions endpoint](/data/reference/forms/form-submissions/list-submissions) to list form submissions for a given form ID.
+This endpoint requires an Enterprise workspace and a workspace token with the `workspace_activity:read` scope. Create a workspace token from your workspace dashboard integrations page to use this endpoint.
-Required scope | `forms:read`
+Required scope | `workspace_activity:read`
@@ -9878,17 +17879,21 @@ Required scope | `forms:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
+import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.sites.forms.list_submissions_by_site(
- site_id="580e63e98c9a982ac9b8b741",
- element_id="18259716-3e5a-646a-5f41-5dc4b9405aa0",
- offset=1,
+client.workspaces.audit_logs.get_workspace_audit_logs(
+ workspace_id_or_slug="hitchhikers-workspace",
limit=1,
+ offset=1,
+ sort_order="asc",
+ event_type="user_access",
+ from_=datetime.datetime.fromisoformat("2025-06-22T16:00:31+00:00"),
+ to=datetime.datetime.fromisoformat("2025-07-22T16:00:31+00:00"),
)
```
@@ -9905,7 +17910,7 @@ client.sites.forms.list_submissions_by_site(
-
-**site_id:** `str` — Unique identifier for a Site
+**workspace_id_or_slug:** `str` — Unique identifier or slug for a Workspace
@@ -9913,7 +17918,7 @@ client.sites.forms.list_submissions_by_site(
-
-**element_id:** `typing.Optional[str]` — Identifier for an element
+**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
@@ -9929,7 +17934,31 @@ client.sites.forms.list_submissions_by_site(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**sort_order:** `typing.Optional[AuditLogsGetWorkspaceAuditLogsRequestSortOrder]` — Sorts the results by asc or desc
+
+
+
+
+
+-
+
+**event_type:** `typing.Optional[AuditLogsGetWorkspaceAuditLogsRequestEventType]` — The event type to filter by
+
+
+
+
+
+-
+
+**from:** `typing.Optional[datetime.datetime]` — The start date to filter by
+
+
+
+
+
+-
+
+**to:** `typing.Optional[datetime.datetime]` — The end date to filter by
@@ -9949,7 +17978,8 @@ client.sites.forms.list_submissions_by_site(
-client.sites.forms.list_submissions(...) -> FormSubmissionList
+## Workspaces Members
+client.workspaces.members.list_members(...) -> WorkspaceMembersResponse
-
@@ -9961,11 +17991,11 @@ client.sites.forms.list_submissions_by_site(
-
-List form submissions for a given form ID within a specific site.
+List the members and invites of a workspace. `invites` includes pending, declined, and expired invites.
-Use the [List Form Submissions by Site endpoint](/data/reference/forms/form-submissions/list-submissions-by-site) to list form submissions for a given site with the ability to filter by a `formElementId`.
+This endpoint requires an Enterprise workspace.
-Required scope | `forms:read`
+Required scope | `workspace_members:read`
@@ -9988,11 +18018,14 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.forms.list_submissions(
- site_id="580e63e98c9a982ac9b8b741",
- form_id="580e63e98c9a982ac9b8b741",
- offset=1,
+client.workspaces.members.list_members(
+ workspace_id="580e63e98c9a982ac9b8b741",
limit=1,
+ email="email",
+ roles=[
+ "owner"
+ ],
+ cursor="cursor",
)
```
@@ -10009,7 +18042,7 @@ client.sites.forms.list_submissions(
-
-**site_id:** `str` — Unique identifier for a Site
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -10017,7 +18050,7 @@ client.sites.forms.list_submissions(
-
-**form_id:** `str` — Unique identifier for a Form
+**limit:** `typing.Optional[int]` — Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
@@ -10025,7 +18058,7 @@ client.sites.forms.list_submissions(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**email:** `typing.Optional[str]` — Filter to the Member or invite with this email address
@@ -10033,7 +18066,15 @@ client.sites.forms.list_submissions(
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**roles:** `typing.Optional[typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]]` — Comma-separated Workspace Roles to filter Members and invites by
+
+
+
+
+
+-
+
+**cursor:** `typing.Optional[str]` — Cursor returned in `pagination.nextCursor` of the previous response
@@ -10053,7 +18094,7 @@ client.sites.forms.list_submissions(
-client.sites.forms.get_submission(...) -> FormSubmission
+client.workspaces.members.invite_member(...) -> InviteWorkspaceMemberResponseBody
-
@@ -10065,9 +18106,29 @@ client.sites.forms.list_submissions(
-
-Get information about a form submission within a specific site.
+Invite a user to a workspace.
-Required scope | `forms:read`
+Set `workspaceRole` to `client` to invite a collaborator client to one Site. Client invites require exactly one `siteAccess` entry. Repeat the request with another Site to extend a compatible pending client invite.
+
+Client invites create collaborator relationships only. This endpoint does not accept `sitePlan` or `canPublish`.
+
+This endpoint requires an Enterprise workspace.
+
+Required scope | `workspace_members:write`
+
+`workspaceRole` and `defaultSiteRole` together decide what the invitee can do and what role the dashboard shows for them once they accept. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+| `workspaceRole` | `defaultSiteRole` | Shown in the dashboard as |
+|---|---|---|
+| `admin` | `site_manager` | Admin |
+| `member` | `site_manager` | Site manager |
+| `member` | `designer` | Designer |
+| `member` | `marketer` | Marketer |
+| `member` | `content_editor` | Content editor |
+| `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+| `reviewer` | `reviewer` | Reviewer |
+
+Admins always have access to every site in the workspace, so an Admin invite can't include a list of specific sites. For Member and Reviewer invites, you can either give the same role on all sites, or list specific sites. In both cases, use the pairings in the table above.
@@ -10082,7 +18143,7 @@ Required scope | `forms:read`
-
```python
-from webflow import Webflow
+from webflow import Webflow, WithDefaultSiteRole
from webflow.environment import WebflowEnvironment
client = Webflow(
@@ -10090,9 +18151,13 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.forms.get_submission(
- site_id="580e63e98c9a982ac9b8b741",
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.workspaces.members.invite_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ request=WithDefaultSiteRole(
+ email="sam.okafor@example.com",
+ workspace_role="member",
+ default_site_role="designer",
+ ),
)
```
@@ -10109,7 +18174,7 @@ client.sites.forms.get_submission(
-
-**site_id:** `str` — Unique identifier for a Site
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -10117,7 +18182,7 @@ client.sites.forms.get_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**request:** `InviteWorkspaceMemberRequestBody`
@@ -10137,7 +18202,7 @@ client.sites.forms.get_submission(
-client.sites.forms.delete_submission(...)
+client.workspaces.members.remove_member(...)
-
@@ -10149,9 +18214,11 @@ client.sites.forms.get_submission(
-
-Delete a form submission within a specific site.
+Remove a member from a workspace.
-Required scope | `forms:write`
+This endpoint requires an Enterprise workspace.
+
+Required scope | `workspace_members:write`
@@ -10174,9 +18241,9 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.forms.delete_submission(
- site_id="580e63e98c9a982ac9b8b741",
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.workspaces.members.remove_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
)
```
@@ -10193,7 +18260,7 @@ client.sites.forms.delete_submission(
-
-**site_id:** `str` — Unique identifier for a Site
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -10201,7 +18268,7 @@ client.sites.forms.delete_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**member_id:** `str` — Unique identifier for a Member
@@ -10221,7 +18288,7 @@ client.sites.forms.delete_submission(
-client.sites.forms.update_submission(...) -> FormSubmission
+client.workspaces.members.update_member_role(...) -> WorkspaceMember
-
@@ -10233,9 +18300,23 @@ client.sites.forms.delete_submission(
-
-Update hidden fields on a form submission within a specific site.
+Update a workspace member's role.
-Required scope | `forms:write`
+This endpoint requires an Enterprise workspace.
+
+Required scope | `workspace_members:write`
+
+`role` and `defaultSiteRole` together decide what the member can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+| `role` | `defaultSiteRole` | Shown in the dashboard as |
+|---|---|---|
+| `admin` | `site_manager` | Admin |
+| `member` | `site_manager` | Site manager |
+| `member` | `designer` | Designer |
+| `member` | `marketer` | Marketer |
+| `member` | `content_editor` | Content editor |
+| `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+| `reviewer` | `reviewer` | Reviewer |
@@ -10258,9 +18339,11 @@ client = Webflow(
environment=WebflowEnvironment.DATA_API,
)
-client.sites.forms.update_submission(
- site_id="580e63e98c9a982ac9b8b741",
- form_submission_id="580e63e98c9a982ac9b8b741",
+client.workspaces.members.update_member_role(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ role="admin",
+ default_site_role="site_manager",
)
```
@@ -10277,7 +18360,7 @@ client.sites.forms.update_submission(
-
-**site_id:** `str` — Unique identifier for a Site
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -10285,7 +18368,7 @@ client.sites.forms.update_submission(
-
-**form_submission_id:** `str` — Unique identifier for a Form Submission
+**member_id:** `str` — Unique identifier for a Member
@@ -10293,7 +18376,23 @@ client.sites.forms.update_submission(
-
-**form_submission_data:** `typing.Optional[typing.Dict[str, typing.Any]]` — An existing **hidden field** defined on the form schema, and the corresponding value to set
+**role:** `UpdateWorkspaceMemberRoleRequestBodyRole` — The Workspace Role to assign
+
+
+
+
+
+-
+
+**default_site_role:** `UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole` — The Member's default Site Role. Required on every request — unlike the dashboard's own role editor, this endpoint never derives it from the Member's current role.
+
+
+
+
+
+-
+
+**default_site_role_id:** `typing.Optional[str]` — ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
@@ -10313,8 +18412,7 @@ client.sites.forms.update_submission(
-## Workspaces AuditLogs
-client.workspaces.audit_logs.get_workspace_audit_logs(...) -> WorkspaceAuditLogResponse
+client.workspaces.members.bulk_update_member_roles(...) -> WorkspaceBulkRoleChangeResponse
-
@@ -10326,11 +18424,25 @@ client.sites.forms.update_submission(
-
-Get audit logs for a workspace.
+Update the role of multiple workspace members and invites in one request. An invite can be updated regardless of status — pending, declined, or expired.
-This endpoint requires an Enterprise workspace and a workspace token with the `workspace_activity:read` scope. Create a workspace token from your workspace dashboard integrations page to use this endpoint.
+This answers `200` once the batch is applied, even when every item failed — a per-item problem is reported in `results` rather than failing the whole request. IDs are deduplicated before processing, so `results` can be shorter than the request; match a result back to your input by `id`, not by position.
-Required scope | `workspace_activity:read`
+This endpoint requires an Enterprise workspace.
+
+Required scope | `workspace_members:write`
+
+`role` and `defaultSiteRole` together decide what every id in the batch can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+| `role` | `defaultSiteRole` | Shown in the dashboard as |
+|---|---|---|
+| `admin` | `site_manager` | Admin |
+| `member` | `site_manager` | Site manager |
+| `member` | `designer` | Designer |
+| `member` | `marketer` | Marketer |
+| `member` | `content_editor` | Content editor |
+| `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+| `reviewer` | `reviewer` | Reviewer |
@@ -10347,21 +18459,23 @@ Required scope | `workspace_activity:read`
```python
from webflow import Webflow
from webflow.environment import WebflowEnvironment
-import datetime
client = Webflow(
access_token="",
environment=WebflowEnvironment.DATA_API,
)
-client.workspaces.audit_logs.get_workspace_audit_logs(
- workspace_id_or_slug="hitchhikers-workspace",
- limit=1,
- offset=1,
- sort_order="asc",
- event_type="user_access",
- from_=datetime.datetime.fromisoformat("2025-06-22T16:00:31+00:00"),
- to=datetime.datetime.fromisoformat("2025-07-22T16:00:31+00:00"),
+client.workspaces.members.bulk_update_member_roles(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_ids=[
+ "62fa5e47f6b8256f4641c5bd",
+ "62fa5e47f6b8256f4641c5be"
+ ],
+ invite_ids=[
+ "63fa5e47f6b8256f4641c5bf"
+ ],
+ role="member",
+ default_site_role="designer",
)
```
@@ -10378,15 +18492,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**workspace_id_or_slug:** `str` — Unique identifier or slug for a Workspace
-
-
-
-
-
--
-
-**limit:** `typing.Optional[int]` — Maximum number of records to be returned (max limit: 100)
+**workspace_id:** `str` — Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
@@ -10394,7 +18500,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**offset:** `typing.Optional[int]` — Offset used for pagination if the results have more than limit records
+**role:** `BulkUpdateWorkspaceMemberRolesRequestBodyRole` — The Workspace Role to assign to every id in the batch
@@ -10402,7 +18508,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**sort_order:** `typing.Optional[AuditLogsGetWorkspaceAuditLogsRequestSortOrder]` — Sorts the results by asc or desc
+**default_site_role:** `BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole` — The default Site Role to assign to every id in the batch
@@ -10410,7 +18516,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**event_type:** `typing.Optional[AuditLogsGetWorkspaceAuditLogsRequestEventType]` — The event type to filter by
+**member_ids:** `typing.Optional[typing.List[str]]` — IDs of active Members to update
@@ -10418,7 +18524,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**from:** `typing.Optional[datetime.datetime]` — The start date to filter by
+**invite_ids:** `typing.Optional[typing.List[str]]` — IDs of invites to update, regardless of status
@@ -10426,7 +18532,7 @@ client.workspaces.audit_logs.get_workspace_audit_logs(
-
-**to:** `typing.Optional[datetime.datetime]` — The end date to filter by
+**default_site_role_id:** `typing.Optional[str]` — ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
diff --git a/src/webflow/__init__.py b/src/webflow/__init__.py
index 48d06737..4b1bdeb0 100644
--- a/src/webflow/__init__.py
+++ b/src/webflow/__init__.py
@@ -7,6 +7,64 @@
if typing.TYPE_CHECKING:
from .types import (
+ AeoAnswerEngine,
+ AeoAnswerEngineKey,
+ AeoAnswerEnginesFilter,
+ AeoBotCategory,
+ AeoBotProvider,
+ AeoBotTrafficFilter,
+ AeoBotTrafficPage,
+ AeoBotTrafficPagesResponse,
+ AeoBotTrafficResponse,
+ AeoBrandNames,
+ AeoCitationFilter,
+ AeoCitationRateResponse,
+ AeoCitationRateResponseBreakdown,
+ AeoCitationRateResponseDataItem,
+ AeoCitationsResponse,
+ AeoCitationsResponseDataItem,
+ AeoCitedDomainPathsResponse,
+ AeoCitedPath,
+ AeoCompetitorCitationsResponse,
+ AeoCompetitorCitationsResponseDataItem,
+ AeoOwnedCitedPath,
+ AeoPage,
+ AeoPageCitationShareResponse,
+ AeoPageCitationShareResponseDataItem,
+ AeoPageCitationShareResponseFilter,
+ AeoPageCitationShareResponsePage,
+ AeoPrompt,
+ AeoPromptExecution,
+ AeoPromptExecutionListResponse,
+ AeoPromptExecutionPromptStatus,
+ AeoPromptExecutionStatus,
+ AeoPromptExecutionTopic,
+ AeoPromptIdsFilter,
+ AeoPromptLabel,
+ AeoPromptListResponse,
+ AeoPromptStatus,
+ AeoPromptTopic,
+ AeoTimeseries,
+ AeoTimeseriesGranularityPeriod,
+ AeoTopCitedDomainsResponse,
+ AeoTopCitedDomainsResponseDataItem,
+ AeoTopicEngineFilter,
+ AeoTopicIdsFilter,
+ AeoTopicLabel,
+ AeoVisibilityBreakdownResponse,
+ AeoVisibilityBreakdownResponseBreakdown,
+ AeoVisibilityBreakdownResponseDataItem,
+ AeoVisibilityFilter,
+ AeoVisibilityResponse,
+ AeoVisibilityResponseDataItem,
+ AeoVisibilityScore,
+ AeoWindow,
+ AnalyzeBucketTimeZone,
+ AnalyzeDailyBucketing,
+ AnalyzeDailyTimeseriesQuery,
+ AnalyzeFilterOperators,
+ AnalyzeTimeOnPageBucketing,
+ AnalyzeWindow,
Application,
Asset,
AssetFolder,
@@ -20,8 +78,18 @@
AuthorizationAuthorizationAuthorizedTo,
AuthorizedUser,
BadRequestErrorBody,
+ BranchCreatedWebhook,
+ BranchCreatedWebhookPayload,
+ BranchDeletedWebhook,
+ BranchDeletedWebhookPayload,
+ BranchMergedWebhook,
+ BranchMergedWebhookPayload,
+ BrowserWindowHeightDataPoint,
+ BrowserWindowHeightFilter,
+ BrowserWindowHeightResponse,
BulkCollectionItem,
BulkCollectionItemFieldData,
+ CollaboratorClientInvite,
Collection,
CollectionItem,
CollectionItemChanged,
@@ -49,6 +117,7 @@
CommentPayload,
CommentPayloadAuthor,
CommentPayloadMentionedUsersItem,
+ CommentPayloadType,
CommentReply,
CommentReplyAuthor,
CommentReplyList,
@@ -67,12 +136,27 @@
ComponentNode,
ComponentProperties,
ComponentProperty,
- ComponentPropertyType,
+ ComponentPropertyText,
+ ComponentPropertyTextType,
Conflict,
CustomCodeBlock,
CustomCodeBlockType,
CustomCodeHostedResponse,
CustomCodeInlineResponse,
+ CustomFont,
+ CustomFontAxis,
+ CustomFontBatchCreateResponse,
+ CustomFontBatchCreateResponseCreatedItem,
+ CustomFontBatchCreateResponseFailedItem,
+ CustomFontBatchDeleteResponse,
+ CustomFontBatchDeleteResponseDeletedItem,
+ CustomFontBatchDeleteResponseFailedItem,
+ CustomFontCreateResponse,
+ CustomFontFontDisplay,
+ CustomFontFormat,
+ CustomFontUpload,
+ CustomFontUploadFields,
+ CustomFonts,
CustomRole,
CustomRoleAuditLogItem,
CustomRoleAuditLogItemEventSubType,
@@ -84,10 +168,10 @@
ErrorCode,
Field,
FieldCreate,
+ FieldGroup,
FieldType,
FieldValidations,
FieldValidationsAdditionalProperties,
- FieldValidationsAdditionalPropertiesAdditionalProperties,
ForbiddenErrorBody,
Form,
FormField,
@@ -101,14 +185,47 @@
FormSubmissionTriggerPayload,
FormSubmissionTriggerPayloadSchemaItem,
FormSubmissionTriggerPayloadSchemaItemFieldType,
+ Goal,
+ GoalCategory,
+ GoalIntegrationSource,
+ GoalPerformanceDataPoint,
+ GoalPerformanceFilter,
+ GoalPerformanceResponse,
+ GoalScope,
+ GoalType,
+ GoalValueType,
+ GoalsResponse,
+ GoogleTagId,
+ GoogleTagIds,
ImageNode,
ImageNodeImage,
+ InsertCollectionItem,
+ InsertCollectionItemFieldData,
+ InsertedCollectionItem,
+ InsertedCollectionItemFieldData,
InvalidDomain,
InvalidScopes,
InventoryItem,
InventoryItemInventoryType,
+ InviteWorkspaceMemberRequestBody,
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole,
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole,
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItem,
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole,
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItem,
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole,
+ InviteWorkspaceMemberRequestBodyOneWorkspaceRole,
+ InviteWorkspaceMemberResponseBody,
+ InviteWorkspaceMemberResponseBodyActive,
+ InviteWorkspaceMemberResponseBody_Active,
+ InviteWorkspaceMemberResponseBody_Invited,
+ Invited,
+ ItemsListItemsLiveRequestCreatedOn,
ItemsListItemsLiveRequestLastPublished,
+ ItemsListItemsLiveRequestLastUpdated,
+ ItemsListItemsRequestCreatedOn,
ItemsListItemsRequestLastPublished,
+ ItemsListItemsRequestLastUpdated,
ListCustomCodeBlocks,
Locale,
Locales,
@@ -126,6 +243,46 @@
Node_TextInput,
NotEnterprisePlanSite,
NotEnterprisePlanWorkspace,
+ Optimization,
+ OptimizationAudienceInsightsResponse,
+ OptimizationDimensionValuesResponse,
+ OptimizationDimensionsResponse,
+ OptimizationGoalsResponse,
+ OptimizationInterval,
+ OptimizationListResponse,
+ OptimizationStatus,
+ OptimizationSummaryResponse,
+ OptimizationTrendResponse,
+ OptimizationType,
+ OptimizeAppliedFilter,
+ OptimizeAppliedFilterEntry,
+ OptimizeAudienceSegment,
+ OptimizeAudienceVariation,
+ OptimizeCurrentGoal,
+ OptimizeDimension,
+ OptimizeDimensionValue,
+ OptimizeFilter,
+ OptimizeFilterOperators,
+ OptimizeGoal,
+ OptimizeGoalCountingMethod,
+ OptimizeGoalMetricType,
+ OptimizeGoalScope,
+ OptimizeGoalSource,
+ OptimizeNonWebflowSiteResource,
+ OptimizeReference,
+ OptimizeReferenceListResponse,
+ OptimizeReferenceResource,
+ OptimizeReferenceResource_OptimizeSite,
+ OptimizeReferenceResource_WebflowSite,
+ OptimizeReportGoal,
+ OptimizeReportIntervalEnd,
+ OptimizeReportIntervalStart,
+ OptimizeTrendDirection,
+ OptimizeTrendGranularity,
+ OptimizeTrendPoint,
+ OptimizeVariationComparison,
+ OptimizeVariationResult,
+ OptimizeWebflowSiteResource,
OptionField,
Order,
OrderAddress,
@@ -156,6 +313,11 @@
PageCreatedWebhookPayload,
PageDeletedWebhook,
PageDeletedWebhookPayload,
+ PageFlowDirection,
+ PageFlowFilter,
+ PageFlowMetricScope,
+ PageFlowResponse,
+ PageFlowRow,
PageList,
PageMetadataUpdatedWebhook,
PageMetadataUpdatedWebhookPayload,
@@ -165,6 +327,7 @@
Payload,
PayloadFieldData,
PaypalDetails,
+ PersonActor,
Product,
ProductAndSkUs,
ProductAndSkUsList,
@@ -184,6 +347,9 @@
ScriptApplyList,
ScriptApplyLocation,
Scripts,
+ ScrollDepthDataPoint,
+ ScrollDepthFilter,
+ ScrollDepthResponse,
SearchButtonNode,
SearchButtonNodeWrite,
Select,
@@ -196,11 +362,19 @@
SingleLocaleCreatedPayloadFieldData,
Site,
SiteActivityLogItem,
+ SiteActivityLogItemActorType,
SiteActivityLogItemEvent,
SiteActivityLogItemResourceOperation,
+ SiteActivityLogItemSource,
SiteActivityLogItemUser,
SiteActivityLogResponse,
SiteDataCollectionType,
+ SiteMember,
+ SiteMemberRoles,
+ SiteMemberRolesSite,
+ SiteMemberRolesWorkspace,
+ SiteMembersResponse,
+ SiteMembersResponsePagination,
SiteMembership,
SiteMembershipAuditLogItem,
SiteMembershipAuditLogItemEventSubType,
@@ -209,6 +383,7 @@
SitePlanName,
SitePublish,
SitePublishPayload,
+ SitePublishPayloadPublishScope,
Sites,
Sku,
SkuFieldData,
@@ -236,7 +411,36 @@
TextNode,
TextNodeText,
TextNodeWrite,
+ TimeOnPageDataPoint,
+ TimeOnPageFilter,
+ TimeOnPageGranularityPeriod,
+ TimeOnPageMetricScope,
+ TimeOnPageResponse,
+ TimeOnPageTimeseriesQuery,
+ TokenActor,
+ TopDimensionsDimension,
+ TopDimensionsFilter,
+ TopDimensionsMetricScope,
+ TopDimensionsResponse,
+ TopDimensionsRow,
+ TopEventsCmsContextEntry,
+ TopEventsComponentContextEntry,
+ TopEventsFilter,
+ TopEventsResponse,
+ TopEventsRow,
+ TopEventsTimeseriesPoint,
+ TopPagesFilter,
+ TopPagesResponse,
+ TopPagesRow,
+ TopPagesSortBy,
+ TopPagesTimeseriesPoint,
+ TrafficDataPoint,
+ TrafficFilter,
+ TrafficMetricScope,
+ TrafficResponse,
TriggerType,
+ UpdateSiteMemberRoleRequestBody,
+ UpdateSiteMemberRoleRequestBodySiteRole,
UpdatedOrder,
UserAccess,
UserAccessAuditLogItem,
@@ -244,9 +448,13 @@
Webhook,
WebhookFilter,
WebhookList,
+ WithDefaultSiteRole,
+ WithSiteAccess,
+ Workspace,
+ WorkspaceAuditLogActor,
+ WorkspaceAuditLogActor_Person,
+ WorkspaceAuditLogActor_Token,
WorkspaceAuditLogItem,
- WorkspaceAuditLogItemActor,
- WorkspaceAuditLogItemPayloadSettingChangeMethod,
WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess,
WorkspaceAuditLogItemPayloadSiteMembershipMethod,
WorkspaceAuditLogItemPayloadSiteMembershipSite,
@@ -268,12 +476,30 @@
WorkspaceAuditLogItem_WorkspaceMembership,
WorkspaceAuditLogItem_WorkspaceSetting,
WorkspaceAuditLogResponse,
+ WorkspaceBulkRoleChangeResponse,
+ WorkspaceBulkRoleChangeResponseResultsItem,
+ WorkspaceBulkRoleChangeResponseResultsItemError,
+ WorkspaceBulkRoleChangeResponseResultsItemErrorCode,
+ WorkspaceBulkRoleChangeResponseResultsItemOutcome,
+ WorkspaceBulkRoleChangeResponseResultsItemType,
+ WorkspaceBulkRoleChangeResponseSummary,
WorkspaceInvitation,
WorkspaceInvitationAuditLogItem,
WorkspaceInvitationAuditLogItemEventSubType,
+ WorkspaceInvite,
+ WorkspaceInviteDefaultSiteRole,
+ WorkspaceInviteRole,
+ WorkspaceInviteStatus,
+ WorkspaceMember,
+ WorkspaceMemberRoles,
+ WorkspaceMemberRolesSite,
+ WorkspaceMemberRolesWorkspace,
+ WorkspaceMembersResponse,
+ WorkspaceMembersResponsePagination,
WorkspaceMembership,
WorkspaceMembershipAuditLogItem,
WorkspaceMembershipAuditLogItemEventSubType,
+ Workspaces,
)
from .errors import (
BadRequestError,
@@ -281,6 +507,7 @@
ForbiddenError,
InternalServerError,
NotFoundError,
+ ServiceUnavailableError,
TooManyRequestsError,
UnauthorizedError,
)
@@ -289,6 +516,13 @@
ComponentPropertiesWritePropertiesItem,
ComponentsUpdateContentResponse,
ComponentsUpdatePropertiesResponse,
+ CustomFontBatchCreateRequestItemsItem,
+ CustomFontBatchCreateRequestItemsItemFontDisplay,
+ CustomFontBatchDeleteRequestItemsItem,
+ CustomFontsCreateRequestFontDisplay,
+ CustomFontsGetResponse,
+ CustomFontsUpdateRequestFontDisplay,
+ CustomFontsUpdateResponse,
EcommInventoryChangedPayload,
InventoryUpdateRequestInventoryType,
OrdersListRequestStatus,
@@ -300,13 +534,17 @@
ProductSkuCreateSku,
ProductsCreateSkuResponse,
SitesPublishResponse,
+ SitesPublishResponsePublishScope,
UpdateStaticContentResponse,
+ analyze,
assets,
collections,
components,
+ custom_fonts,
ecommerce,
forms,
inventory,
+ optimize,
orders,
pages,
products,
@@ -320,6 +558,64 @@
from .environment import WebflowEnvironment
from .version import __version__
_dynamic_imports: typing.Dict[str, str] = {
+ "AeoAnswerEngine": ".types",
+ "AeoAnswerEngineKey": ".types",
+ "AeoAnswerEnginesFilter": ".types",
+ "AeoBotCategory": ".types",
+ "AeoBotProvider": ".types",
+ "AeoBotTrafficFilter": ".types",
+ "AeoBotTrafficPage": ".types",
+ "AeoBotTrafficPagesResponse": ".types",
+ "AeoBotTrafficResponse": ".types",
+ "AeoBrandNames": ".types",
+ "AeoCitationFilter": ".types",
+ "AeoCitationRateResponse": ".types",
+ "AeoCitationRateResponseBreakdown": ".types",
+ "AeoCitationRateResponseDataItem": ".types",
+ "AeoCitationsResponse": ".types",
+ "AeoCitationsResponseDataItem": ".types",
+ "AeoCitedDomainPathsResponse": ".types",
+ "AeoCitedPath": ".types",
+ "AeoCompetitorCitationsResponse": ".types",
+ "AeoCompetitorCitationsResponseDataItem": ".types",
+ "AeoOwnedCitedPath": ".types",
+ "AeoPage": ".types",
+ "AeoPageCitationShareResponse": ".types",
+ "AeoPageCitationShareResponseDataItem": ".types",
+ "AeoPageCitationShareResponseFilter": ".types",
+ "AeoPageCitationShareResponsePage": ".types",
+ "AeoPrompt": ".types",
+ "AeoPromptExecution": ".types",
+ "AeoPromptExecutionListResponse": ".types",
+ "AeoPromptExecutionPromptStatus": ".types",
+ "AeoPromptExecutionStatus": ".types",
+ "AeoPromptExecutionTopic": ".types",
+ "AeoPromptIdsFilter": ".types",
+ "AeoPromptLabel": ".types",
+ "AeoPromptListResponse": ".types",
+ "AeoPromptStatus": ".types",
+ "AeoPromptTopic": ".types",
+ "AeoTimeseries": ".types",
+ "AeoTimeseriesGranularityPeriod": ".types",
+ "AeoTopCitedDomainsResponse": ".types",
+ "AeoTopCitedDomainsResponseDataItem": ".types",
+ "AeoTopicEngineFilter": ".types",
+ "AeoTopicIdsFilter": ".types",
+ "AeoTopicLabel": ".types",
+ "AeoVisibilityBreakdownResponse": ".types",
+ "AeoVisibilityBreakdownResponseBreakdown": ".types",
+ "AeoVisibilityBreakdownResponseDataItem": ".types",
+ "AeoVisibilityFilter": ".types",
+ "AeoVisibilityResponse": ".types",
+ "AeoVisibilityResponseDataItem": ".types",
+ "AeoVisibilityScore": ".types",
+ "AeoWindow": ".types",
+ "AnalyzeBucketTimeZone": ".types",
+ "AnalyzeDailyBucketing": ".types",
+ "AnalyzeDailyTimeseriesQuery": ".types",
+ "AnalyzeFilterOperators": ".types",
+ "AnalyzeTimeOnPageBucketing": ".types",
+ "AnalyzeWindow": ".types",
"Application": ".types",
"Asset": ".types",
"AssetFolder": ".types",
@@ -335,8 +631,18 @@
"AuthorizedUser": ".types",
"BadRequestError": ".errors",
"BadRequestErrorBody": ".types",
+ "BranchCreatedWebhook": ".types",
+ "BranchCreatedWebhookPayload": ".types",
+ "BranchDeletedWebhook": ".types",
+ "BranchDeletedWebhookPayload": ".types",
+ "BranchMergedWebhook": ".types",
+ "BranchMergedWebhookPayload": ".types",
+ "BrowserWindowHeightDataPoint": ".types",
+ "BrowserWindowHeightFilter": ".types",
+ "BrowserWindowHeightResponse": ".types",
"BulkCollectionItem": ".types",
"BulkCollectionItemFieldData": ".types",
+ "CollaboratorClientInvite": ".types",
"Collection": ".types",
"CollectionItem": ".types",
"CollectionItemChanged": ".types",
@@ -364,6 +670,7 @@
"CommentPayload": ".types",
"CommentPayloadAuthor": ".types",
"CommentPayloadMentionedUsersItem": ".types",
+ "CommentPayloadType": ".types",
"CommentReply": ".types",
"CommentReplyAuthor": ".types",
"CommentReplyList": ".types",
@@ -384,7 +691,8 @@
"ComponentProperties": ".types",
"ComponentPropertiesWritePropertiesItem": ".resources",
"ComponentProperty": ".types",
- "ComponentPropertyType": ".types",
+ "ComponentPropertyText": ".types",
+ "ComponentPropertyTextType": ".types",
"ComponentsUpdateContentResponse": ".resources",
"ComponentsUpdatePropertiesResponse": ".resources",
"Conflict": ".types",
@@ -393,6 +701,27 @@
"CustomCodeBlockType": ".types",
"CustomCodeHostedResponse": ".types",
"CustomCodeInlineResponse": ".types",
+ "CustomFont": ".types",
+ "CustomFontAxis": ".types",
+ "CustomFontBatchCreateRequestItemsItem": ".resources",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay": ".resources",
+ "CustomFontBatchCreateResponse": ".types",
+ "CustomFontBatchCreateResponseCreatedItem": ".types",
+ "CustomFontBatchCreateResponseFailedItem": ".types",
+ "CustomFontBatchDeleteRequestItemsItem": ".resources",
+ "CustomFontBatchDeleteResponse": ".types",
+ "CustomFontBatchDeleteResponseDeletedItem": ".types",
+ "CustomFontBatchDeleteResponseFailedItem": ".types",
+ "CustomFontCreateResponse": ".types",
+ "CustomFontFontDisplay": ".types",
+ "CustomFontFormat": ".types",
+ "CustomFontUpload": ".types",
+ "CustomFontUploadFields": ".types",
+ "CustomFonts": ".types",
+ "CustomFontsCreateRequestFontDisplay": ".resources",
+ "CustomFontsGetResponse": ".resources",
+ "CustomFontsUpdateRequestFontDisplay": ".resources",
+ "CustomFontsUpdateResponse": ".resources",
"CustomRole": ".types",
"CustomRoleAuditLogItem": ".types",
"CustomRoleAuditLogItemEventSubType": ".types",
@@ -405,10 +734,10 @@
"ErrorCode": ".types",
"Field": ".types",
"FieldCreate": ".types",
+ "FieldGroup": ".types",
"FieldType": ".types",
"FieldValidations": ".types",
"FieldValidationsAdditionalProperties": ".types",
- "FieldValidationsAdditionalPropertiesAdditionalProperties": ".types",
"ForbiddenError": ".errors",
"ForbiddenErrorBody": ".types",
"Form": ".types",
@@ -423,16 +752,49 @@
"FormSubmissionTriggerPayload": ".types",
"FormSubmissionTriggerPayloadSchemaItem": ".types",
"FormSubmissionTriggerPayloadSchemaItemFieldType": ".types",
+ "Goal": ".types",
+ "GoalCategory": ".types",
+ "GoalIntegrationSource": ".types",
+ "GoalPerformanceDataPoint": ".types",
+ "GoalPerformanceFilter": ".types",
+ "GoalPerformanceResponse": ".types",
+ "GoalScope": ".types",
+ "GoalType": ".types",
+ "GoalValueType": ".types",
+ "GoalsResponse": ".types",
+ "GoogleTagId": ".types",
+ "GoogleTagIds": ".types",
"ImageNode": ".types",
"ImageNodeImage": ".types",
+ "InsertCollectionItem": ".types",
+ "InsertCollectionItemFieldData": ".types",
+ "InsertedCollectionItem": ".types",
+ "InsertedCollectionItemFieldData": ".types",
"InternalServerError": ".errors",
"InvalidDomain": ".types",
"InvalidScopes": ".types",
"InventoryItem": ".types",
"InventoryItemInventoryType": ".types",
"InventoryUpdateRequestInventoryType": ".resources",
+ "InviteWorkspaceMemberRequestBody": ".types",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole": ".types",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole": ".types",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItem": ".types",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole": ".types",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItem": ".types",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole": ".types",
+ "InviteWorkspaceMemberRequestBodyOneWorkspaceRole": ".types",
+ "InviteWorkspaceMemberResponseBody": ".types",
+ "InviteWorkspaceMemberResponseBodyActive": ".types",
+ "InviteWorkspaceMemberResponseBody_Active": ".types",
+ "InviteWorkspaceMemberResponseBody_Invited": ".types",
+ "Invited": ".types",
+ "ItemsListItemsLiveRequestCreatedOn": ".types",
"ItemsListItemsLiveRequestLastPublished": ".types",
+ "ItemsListItemsLiveRequestLastUpdated": ".types",
+ "ItemsListItemsRequestCreatedOn": ".types",
"ItemsListItemsRequestLastPublished": ".types",
+ "ItemsListItemsRequestLastUpdated": ".types",
"ListCustomCodeBlocks": ".types",
"Locale": ".types",
"Locales": ".types",
@@ -451,6 +813,46 @@
"NotEnterprisePlanSite": ".types",
"NotEnterprisePlanWorkspace": ".types",
"NotFoundError": ".errors",
+ "Optimization": ".types",
+ "OptimizationAudienceInsightsResponse": ".types",
+ "OptimizationDimensionValuesResponse": ".types",
+ "OptimizationDimensionsResponse": ".types",
+ "OptimizationGoalsResponse": ".types",
+ "OptimizationInterval": ".types",
+ "OptimizationListResponse": ".types",
+ "OptimizationStatus": ".types",
+ "OptimizationSummaryResponse": ".types",
+ "OptimizationTrendResponse": ".types",
+ "OptimizationType": ".types",
+ "OptimizeAppliedFilter": ".types",
+ "OptimizeAppliedFilterEntry": ".types",
+ "OptimizeAudienceSegment": ".types",
+ "OptimizeAudienceVariation": ".types",
+ "OptimizeCurrentGoal": ".types",
+ "OptimizeDimension": ".types",
+ "OptimizeDimensionValue": ".types",
+ "OptimizeFilter": ".types",
+ "OptimizeFilterOperators": ".types",
+ "OptimizeGoal": ".types",
+ "OptimizeGoalCountingMethod": ".types",
+ "OptimizeGoalMetricType": ".types",
+ "OptimizeGoalScope": ".types",
+ "OptimizeGoalSource": ".types",
+ "OptimizeNonWebflowSiteResource": ".types",
+ "OptimizeReference": ".types",
+ "OptimizeReferenceListResponse": ".types",
+ "OptimizeReferenceResource": ".types",
+ "OptimizeReferenceResource_OptimizeSite": ".types",
+ "OptimizeReferenceResource_WebflowSite": ".types",
+ "OptimizeReportGoal": ".types",
+ "OptimizeReportIntervalEnd": ".types",
+ "OptimizeReportIntervalStart": ".types",
+ "OptimizeTrendDirection": ".types",
+ "OptimizeTrendGranularity": ".types",
+ "OptimizeTrendPoint": ".types",
+ "OptimizeVariationComparison": ".types",
+ "OptimizeVariationResult": ".types",
+ "OptimizeWebflowSiteResource": ".types",
"OptionField": ".types",
"Order": ".types",
"OrderAddress": ".types",
@@ -484,6 +886,11 @@
"PageDeletedWebhook": ".types",
"PageDeletedWebhookPayload": ".types",
"PageDomWriteNodesItem": ".resources",
+ "PageFlowDirection": ".types",
+ "PageFlowFilter": ".types",
+ "PageFlowMetricScope": ".types",
+ "PageFlowResponse": ".types",
+ "PageFlowRow": ".types",
"PageList": ".types",
"PageMetadataUpdatedWebhook": ".types",
"PageMetadataUpdatedWebhookPayload": ".types",
@@ -495,6 +902,7 @@
"Payload": ".types",
"PayloadFieldData": ".types",
"PaypalDetails": ".types",
+ "PersonActor": ".types",
"Product": ".types",
"ProductAndSkUs": ".types",
"ProductAndSkUsList": ".types",
@@ -517,23 +925,35 @@
"ScriptApplyList": ".types",
"ScriptApplyLocation": ".types",
"Scripts": ".types",
+ "ScrollDepthDataPoint": ".types",
+ "ScrollDepthFilter": ".types",
+ "ScrollDepthResponse": ".types",
"SearchButtonNode": ".types",
"SearchButtonNodeWrite": ".types",
"Select": ".types",
"SelectNode": ".types",
"SelectNodeChoicesItem": ".types",
"SelectNodeWriteChoicesItem": ".types",
+ "ServiceUnavailableError": ".errors",
"SettingChange": ".types",
"SettingChangeAuditLogItem": ".types",
"SingleLocaleCreatedPayload": ".types",
"SingleLocaleCreatedPayloadFieldData": ".types",
"Site": ".types",
"SiteActivityLogItem": ".types",
+ "SiteActivityLogItemActorType": ".types",
"SiteActivityLogItemEvent": ".types",
"SiteActivityLogItemResourceOperation": ".types",
+ "SiteActivityLogItemSource": ".types",
"SiteActivityLogItemUser": ".types",
"SiteActivityLogResponse": ".types",
"SiteDataCollectionType": ".types",
+ "SiteMember": ".types",
+ "SiteMemberRoles": ".types",
+ "SiteMemberRolesSite": ".types",
+ "SiteMemberRolesWorkspace": ".types",
+ "SiteMembersResponse": ".types",
+ "SiteMembersResponsePagination": ".types",
"SiteMembership": ".types",
"SiteMembershipAuditLogItem": ".types",
"SiteMembershipAuditLogItemEventSubType": ".types",
@@ -542,8 +962,10 @@
"SitePlanName": ".types",
"SitePublish": ".types",
"SitePublishPayload": ".types",
+ "SitePublishPayloadPublishScope": ".types",
"Sites": ".types",
"SitesPublishResponse": ".resources",
+ "SitesPublishResponsePublishScope": ".resources",
"Sku": ".types",
"SkuFieldData": ".types",
"SkuFieldDataCompareAtPrice": ".types",
@@ -570,9 +992,38 @@
"TextNode": ".types",
"TextNodeText": ".types",
"TextNodeWrite": ".types",
+ "TimeOnPageDataPoint": ".types",
+ "TimeOnPageFilter": ".types",
+ "TimeOnPageGranularityPeriod": ".types",
+ "TimeOnPageMetricScope": ".types",
+ "TimeOnPageResponse": ".types",
+ "TimeOnPageTimeseriesQuery": ".types",
+ "TokenActor": ".types",
"TooManyRequestsError": ".errors",
+ "TopDimensionsDimension": ".types",
+ "TopDimensionsFilter": ".types",
+ "TopDimensionsMetricScope": ".types",
+ "TopDimensionsResponse": ".types",
+ "TopDimensionsRow": ".types",
+ "TopEventsCmsContextEntry": ".types",
+ "TopEventsComponentContextEntry": ".types",
+ "TopEventsFilter": ".types",
+ "TopEventsResponse": ".types",
+ "TopEventsRow": ".types",
+ "TopEventsTimeseriesPoint": ".types",
+ "TopPagesFilter": ".types",
+ "TopPagesResponse": ".types",
+ "TopPagesRow": ".types",
+ "TopPagesSortBy": ".types",
+ "TopPagesTimeseriesPoint": ".types",
+ "TrafficDataPoint": ".types",
+ "TrafficFilter": ".types",
+ "TrafficMetricScope": ".types",
+ "TrafficResponse": ".types",
"TriggerType": ".types",
"UnauthorizedError": ".errors",
+ "UpdateSiteMemberRoleRequestBody": ".types",
+ "UpdateSiteMemberRoleRequestBodySiteRole": ".types",
"UpdateStaticContentResponse": ".resources",
"UpdatedOrder": ".types",
"UserAccess": ".types",
@@ -583,9 +1034,13 @@
"Webhook": ".types",
"WebhookFilter": ".types",
"WebhookList": ".types",
+ "WithDefaultSiteRole": ".types",
+ "WithSiteAccess": ".types",
+ "Workspace": ".types",
+ "WorkspaceAuditLogActor": ".types",
+ "WorkspaceAuditLogActor_Person": ".types",
+ "WorkspaceAuditLogActor_Token": ".types",
"WorkspaceAuditLogItem": ".types",
- "WorkspaceAuditLogItemActor": ".types",
- "WorkspaceAuditLogItemPayloadSettingChangeMethod": ".types",
"WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess": ".types",
"WorkspaceAuditLogItemPayloadSiteMembershipMethod": ".types",
"WorkspaceAuditLogItemPayloadSiteMembershipSite": ".types",
@@ -607,19 +1062,40 @@
"WorkspaceAuditLogItem_WorkspaceMembership": ".types",
"WorkspaceAuditLogItem_WorkspaceSetting": ".types",
"WorkspaceAuditLogResponse": ".types",
+ "WorkspaceBulkRoleChangeResponse": ".types",
+ "WorkspaceBulkRoleChangeResponseResultsItem": ".types",
+ "WorkspaceBulkRoleChangeResponseResultsItemError": ".types",
+ "WorkspaceBulkRoleChangeResponseResultsItemErrorCode": ".types",
+ "WorkspaceBulkRoleChangeResponseResultsItemOutcome": ".types",
+ "WorkspaceBulkRoleChangeResponseResultsItemType": ".types",
+ "WorkspaceBulkRoleChangeResponseSummary": ".types",
"WorkspaceInvitation": ".types",
"WorkspaceInvitationAuditLogItem": ".types",
"WorkspaceInvitationAuditLogItemEventSubType": ".types",
+ "WorkspaceInvite": ".types",
+ "WorkspaceInviteDefaultSiteRole": ".types",
+ "WorkspaceInviteRole": ".types",
+ "WorkspaceInviteStatus": ".types",
+ "WorkspaceMember": ".types",
+ "WorkspaceMemberRoles": ".types",
+ "WorkspaceMemberRolesSite": ".types",
+ "WorkspaceMemberRolesWorkspace": ".types",
+ "WorkspaceMembersResponse": ".types",
+ "WorkspaceMembersResponsePagination": ".types",
"WorkspaceMembership": ".types",
"WorkspaceMembershipAuditLogItem": ".types",
"WorkspaceMembershipAuditLogItemEventSubType": ".types",
+ "Workspaces": ".types",
"__version__": ".version",
+ "analyze": ".resources",
"assets": ".resources",
"collections": ".resources",
"components": ".resources",
+ "custom_fonts": ".resources",
"ecommerce": ".resources",
"forms": ".resources",
"inventory": ".resources",
+ "optimize": ".resources",
"orders": ".resources",
"pages": ".resources",
"products": ".resources",
@@ -653,6 +1129,64 @@ def __dir__():
__all__ = [
+ "AeoAnswerEngine",
+ "AeoAnswerEngineKey",
+ "AeoAnswerEnginesFilter",
+ "AeoBotCategory",
+ "AeoBotProvider",
+ "AeoBotTrafficFilter",
+ "AeoBotTrafficPage",
+ "AeoBotTrafficPagesResponse",
+ "AeoBotTrafficResponse",
+ "AeoBrandNames",
+ "AeoCitationFilter",
+ "AeoCitationRateResponse",
+ "AeoCitationRateResponseBreakdown",
+ "AeoCitationRateResponseDataItem",
+ "AeoCitationsResponse",
+ "AeoCitationsResponseDataItem",
+ "AeoCitedDomainPathsResponse",
+ "AeoCitedPath",
+ "AeoCompetitorCitationsResponse",
+ "AeoCompetitorCitationsResponseDataItem",
+ "AeoOwnedCitedPath",
+ "AeoPage",
+ "AeoPageCitationShareResponse",
+ "AeoPageCitationShareResponseDataItem",
+ "AeoPageCitationShareResponseFilter",
+ "AeoPageCitationShareResponsePage",
+ "AeoPrompt",
+ "AeoPromptExecution",
+ "AeoPromptExecutionListResponse",
+ "AeoPromptExecutionPromptStatus",
+ "AeoPromptExecutionStatus",
+ "AeoPromptExecutionTopic",
+ "AeoPromptIdsFilter",
+ "AeoPromptLabel",
+ "AeoPromptListResponse",
+ "AeoPromptStatus",
+ "AeoPromptTopic",
+ "AeoTimeseries",
+ "AeoTimeseriesGranularityPeriod",
+ "AeoTopCitedDomainsResponse",
+ "AeoTopCitedDomainsResponseDataItem",
+ "AeoTopicEngineFilter",
+ "AeoTopicIdsFilter",
+ "AeoTopicLabel",
+ "AeoVisibilityBreakdownResponse",
+ "AeoVisibilityBreakdownResponseBreakdown",
+ "AeoVisibilityBreakdownResponseDataItem",
+ "AeoVisibilityFilter",
+ "AeoVisibilityResponse",
+ "AeoVisibilityResponseDataItem",
+ "AeoVisibilityScore",
+ "AeoWindow",
+ "AnalyzeBucketTimeZone",
+ "AnalyzeDailyBucketing",
+ "AnalyzeDailyTimeseriesQuery",
+ "AnalyzeFilterOperators",
+ "AnalyzeTimeOnPageBucketing",
+ "AnalyzeWindow",
"Application",
"Asset",
"AssetFolder",
@@ -668,8 +1202,18 @@ def __dir__():
"AuthorizedUser",
"BadRequestError",
"BadRequestErrorBody",
+ "BranchCreatedWebhook",
+ "BranchCreatedWebhookPayload",
+ "BranchDeletedWebhook",
+ "BranchDeletedWebhookPayload",
+ "BranchMergedWebhook",
+ "BranchMergedWebhookPayload",
+ "BrowserWindowHeightDataPoint",
+ "BrowserWindowHeightFilter",
+ "BrowserWindowHeightResponse",
"BulkCollectionItem",
"BulkCollectionItemFieldData",
+ "CollaboratorClientInvite",
"Collection",
"CollectionItem",
"CollectionItemChanged",
@@ -697,6 +1241,7 @@ def __dir__():
"CommentPayload",
"CommentPayloadAuthor",
"CommentPayloadMentionedUsersItem",
+ "CommentPayloadType",
"CommentReply",
"CommentReplyAuthor",
"CommentReplyList",
@@ -717,7 +1262,8 @@ def __dir__():
"ComponentProperties",
"ComponentPropertiesWritePropertiesItem",
"ComponentProperty",
- "ComponentPropertyType",
+ "ComponentPropertyText",
+ "ComponentPropertyTextType",
"ComponentsUpdateContentResponse",
"ComponentsUpdatePropertiesResponse",
"Conflict",
@@ -726,6 +1272,27 @@ def __dir__():
"CustomCodeBlockType",
"CustomCodeHostedResponse",
"CustomCodeInlineResponse",
+ "CustomFont",
+ "CustomFontAxis",
+ "CustomFontBatchCreateRequestItemsItem",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay",
+ "CustomFontBatchCreateResponse",
+ "CustomFontBatchCreateResponseCreatedItem",
+ "CustomFontBatchCreateResponseFailedItem",
+ "CustomFontBatchDeleteRequestItemsItem",
+ "CustomFontBatchDeleteResponse",
+ "CustomFontBatchDeleteResponseDeletedItem",
+ "CustomFontBatchDeleteResponseFailedItem",
+ "CustomFontCreateResponse",
+ "CustomFontFontDisplay",
+ "CustomFontFormat",
+ "CustomFontUpload",
+ "CustomFontUploadFields",
+ "CustomFonts",
+ "CustomFontsCreateRequestFontDisplay",
+ "CustomFontsGetResponse",
+ "CustomFontsUpdateRequestFontDisplay",
+ "CustomFontsUpdateResponse",
"CustomRole",
"CustomRoleAuditLogItem",
"CustomRoleAuditLogItemEventSubType",
@@ -738,10 +1305,10 @@ def __dir__():
"ErrorCode",
"Field",
"FieldCreate",
+ "FieldGroup",
"FieldType",
"FieldValidations",
"FieldValidationsAdditionalProperties",
- "FieldValidationsAdditionalPropertiesAdditionalProperties",
"ForbiddenError",
"ForbiddenErrorBody",
"Form",
@@ -756,16 +1323,49 @@ def __dir__():
"FormSubmissionTriggerPayload",
"FormSubmissionTriggerPayloadSchemaItem",
"FormSubmissionTriggerPayloadSchemaItemFieldType",
+ "Goal",
+ "GoalCategory",
+ "GoalIntegrationSource",
+ "GoalPerformanceDataPoint",
+ "GoalPerformanceFilter",
+ "GoalPerformanceResponse",
+ "GoalScope",
+ "GoalType",
+ "GoalValueType",
+ "GoalsResponse",
+ "GoogleTagId",
+ "GoogleTagIds",
"ImageNode",
"ImageNodeImage",
+ "InsertCollectionItem",
+ "InsertCollectionItemFieldData",
+ "InsertedCollectionItem",
+ "InsertedCollectionItemFieldData",
"InternalServerError",
"InvalidDomain",
"InvalidScopes",
"InventoryItem",
"InventoryItemInventoryType",
"InventoryUpdateRequestInventoryType",
+ "InviteWorkspaceMemberRequestBody",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItem",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItem",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole",
+ "InviteWorkspaceMemberRequestBodyOneWorkspaceRole",
+ "InviteWorkspaceMemberResponseBody",
+ "InviteWorkspaceMemberResponseBodyActive",
+ "InviteWorkspaceMemberResponseBody_Active",
+ "InviteWorkspaceMemberResponseBody_Invited",
+ "Invited",
+ "ItemsListItemsLiveRequestCreatedOn",
"ItemsListItemsLiveRequestLastPublished",
+ "ItemsListItemsLiveRequestLastUpdated",
+ "ItemsListItemsRequestCreatedOn",
"ItemsListItemsRequestLastPublished",
+ "ItemsListItemsRequestLastUpdated",
"ListCustomCodeBlocks",
"Locale",
"Locales",
@@ -784,6 +1384,46 @@ def __dir__():
"NotEnterprisePlanSite",
"NotEnterprisePlanWorkspace",
"NotFoundError",
+ "Optimization",
+ "OptimizationAudienceInsightsResponse",
+ "OptimizationDimensionValuesResponse",
+ "OptimizationDimensionsResponse",
+ "OptimizationGoalsResponse",
+ "OptimizationInterval",
+ "OptimizationListResponse",
+ "OptimizationStatus",
+ "OptimizationSummaryResponse",
+ "OptimizationTrendResponse",
+ "OptimizationType",
+ "OptimizeAppliedFilter",
+ "OptimizeAppliedFilterEntry",
+ "OptimizeAudienceSegment",
+ "OptimizeAudienceVariation",
+ "OptimizeCurrentGoal",
+ "OptimizeDimension",
+ "OptimizeDimensionValue",
+ "OptimizeFilter",
+ "OptimizeFilterOperators",
+ "OptimizeGoal",
+ "OptimizeGoalCountingMethod",
+ "OptimizeGoalMetricType",
+ "OptimizeGoalScope",
+ "OptimizeGoalSource",
+ "OptimizeNonWebflowSiteResource",
+ "OptimizeReference",
+ "OptimizeReferenceListResponse",
+ "OptimizeReferenceResource",
+ "OptimizeReferenceResource_OptimizeSite",
+ "OptimizeReferenceResource_WebflowSite",
+ "OptimizeReportGoal",
+ "OptimizeReportIntervalEnd",
+ "OptimizeReportIntervalStart",
+ "OptimizeTrendDirection",
+ "OptimizeTrendGranularity",
+ "OptimizeTrendPoint",
+ "OptimizeVariationComparison",
+ "OptimizeVariationResult",
+ "OptimizeWebflowSiteResource",
"OptionField",
"Order",
"OrderAddress",
@@ -817,6 +1457,11 @@ def __dir__():
"PageDeletedWebhook",
"PageDeletedWebhookPayload",
"PageDomWriteNodesItem",
+ "PageFlowDirection",
+ "PageFlowFilter",
+ "PageFlowMetricScope",
+ "PageFlowResponse",
+ "PageFlowRow",
"PageList",
"PageMetadataUpdatedWebhook",
"PageMetadataUpdatedWebhookPayload",
@@ -828,6 +1473,7 @@ def __dir__():
"Payload",
"PayloadFieldData",
"PaypalDetails",
+ "PersonActor",
"Product",
"ProductAndSkUs",
"ProductAndSkUsList",
@@ -850,23 +1496,35 @@ def __dir__():
"ScriptApplyList",
"ScriptApplyLocation",
"Scripts",
+ "ScrollDepthDataPoint",
+ "ScrollDepthFilter",
+ "ScrollDepthResponse",
"SearchButtonNode",
"SearchButtonNodeWrite",
"Select",
"SelectNode",
"SelectNodeChoicesItem",
"SelectNodeWriteChoicesItem",
+ "ServiceUnavailableError",
"SettingChange",
"SettingChangeAuditLogItem",
"SingleLocaleCreatedPayload",
"SingleLocaleCreatedPayloadFieldData",
"Site",
"SiteActivityLogItem",
+ "SiteActivityLogItemActorType",
"SiteActivityLogItemEvent",
"SiteActivityLogItemResourceOperation",
+ "SiteActivityLogItemSource",
"SiteActivityLogItemUser",
"SiteActivityLogResponse",
"SiteDataCollectionType",
+ "SiteMember",
+ "SiteMemberRoles",
+ "SiteMemberRolesSite",
+ "SiteMemberRolesWorkspace",
+ "SiteMembersResponse",
+ "SiteMembersResponsePagination",
"SiteMembership",
"SiteMembershipAuditLogItem",
"SiteMembershipAuditLogItemEventSubType",
@@ -875,8 +1533,10 @@ def __dir__():
"SitePlanName",
"SitePublish",
"SitePublishPayload",
+ "SitePublishPayloadPublishScope",
"Sites",
"SitesPublishResponse",
+ "SitesPublishResponsePublishScope",
"Sku",
"SkuFieldData",
"SkuFieldDataCompareAtPrice",
@@ -903,9 +1563,38 @@ def __dir__():
"TextNode",
"TextNodeText",
"TextNodeWrite",
+ "TimeOnPageDataPoint",
+ "TimeOnPageFilter",
+ "TimeOnPageGranularityPeriod",
+ "TimeOnPageMetricScope",
+ "TimeOnPageResponse",
+ "TimeOnPageTimeseriesQuery",
+ "TokenActor",
"TooManyRequestsError",
+ "TopDimensionsDimension",
+ "TopDimensionsFilter",
+ "TopDimensionsMetricScope",
+ "TopDimensionsResponse",
+ "TopDimensionsRow",
+ "TopEventsCmsContextEntry",
+ "TopEventsComponentContextEntry",
+ "TopEventsFilter",
+ "TopEventsResponse",
+ "TopEventsRow",
+ "TopEventsTimeseriesPoint",
+ "TopPagesFilter",
+ "TopPagesResponse",
+ "TopPagesRow",
+ "TopPagesSortBy",
+ "TopPagesTimeseriesPoint",
+ "TrafficDataPoint",
+ "TrafficFilter",
+ "TrafficMetricScope",
+ "TrafficResponse",
"TriggerType",
"UnauthorizedError",
+ "UpdateSiteMemberRoleRequestBody",
+ "UpdateSiteMemberRoleRequestBodySiteRole",
"UpdateStaticContentResponse",
"UpdatedOrder",
"UserAccess",
@@ -916,9 +1605,13 @@ def __dir__():
"Webhook",
"WebhookFilter",
"WebhookList",
+ "WithDefaultSiteRole",
+ "WithSiteAccess",
+ "Workspace",
+ "WorkspaceAuditLogActor",
+ "WorkspaceAuditLogActor_Person",
+ "WorkspaceAuditLogActor_Token",
"WorkspaceAuditLogItem",
- "WorkspaceAuditLogItemActor",
- "WorkspaceAuditLogItemPayloadSettingChangeMethod",
"WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess",
"WorkspaceAuditLogItemPayloadSiteMembershipMethod",
"WorkspaceAuditLogItemPayloadSiteMembershipSite",
@@ -940,19 +1633,40 @@ def __dir__():
"WorkspaceAuditLogItem_WorkspaceMembership",
"WorkspaceAuditLogItem_WorkspaceSetting",
"WorkspaceAuditLogResponse",
+ "WorkspaceBulkRoleChangeResponse",
+ "WorkspaceBulkRoleChangeResponseResultsItem",
+ "WorkspaceBulkRoleChangeResponseResultsItemError",
+ "WorkspaceBulkRoleChangeResponseResultsItemErrorCode",
+ "WorkspaceBulkRoleChangeResponseResultsItemOutcome",
+ "WorkspaceBulkRoleChangeResponseResultsItemType",
+ "WorkspaceBulkRoleChangeResponseSummary",
"WorkspaceInvitation",
"WorkspaceInvitationAuditLogItem",
"WorkspaceInvitationAuditLogItemEventSubType",
+ "WorkspaceInvite",
+ "WorkspaceInviteDefaultSiteRole",
+ "WorkspaceInviteRole",
+ "WorkspaceInviteStatus",
+ "WorkspaceMember",
+ "WorkspaceMemberRoles",
+ "WorkspaceMemberRolesSite",
+ "WorkspaceMemberRolesWorkspace",
+ "WorkspaceMembersResponse",
+ "WorkspaceMembersResponsePagination",
"WorkspaceMembership",
"WorkspaceMembershipAuditLogItem",
"WorkspaceMembershipAuditLogItemEventSubType",
+ "Workspaces",
"__version__",
+ "analyze",
"assets",
"collections",
"components",
+ "custom_fonts",
"ecommerce",
"forms",
"inventory",
+ "optimize",
"orders",
"pages",
"products",
diff --git a/src/webflow/client.py b/src/webflow/client.py
index bc75156d..99e27bc5 100644
--- a/src/webflow/client.py
+++ b/src/webflow/client.py
@@ -10,12 +10,15 @@
from .environment import WebflowEnvironment
if typing.TYPE_CHECKING:
+ from .resources.analyze.client import AnalyzeClient, AsyncAnalyzeClient
from .resources.assets.client import AssetsClient, AsyncAssetsClient
from .resources.collections.client import AsyncCollectionsClient, CollectionsClient
from .resources.components.client import AsyncComponentsClient, ComponentsClient
+ from .resources.custom_fonts.client import AsyncCustomFontsClient, CustomFontsClient
from .resources.ecommerce.client import AsyncEcommerceClient, EcommerceClient
from .resources.forms.client import AsyncFormsClient, FormsClient
from .resources.inventory.client import AsyncInventoryClient, InventoryClient
+ from .resources.optimize.client import AsyncOptimizeClient, OptimizeClient
from .resources.orders.client import AsyncOrdersClient, OrdersClient
from .resources.pages.client import AsyncPagesClient, PagesClient
from .resources.products.client import AsyncProductsClient, ProductsClient
@@ -93,19 +96,22 @@ def __init__(
logging=logging,
)
self._token: typing.Optional[TokenClient] = None
+ self._workspaces: typing.Optional[WorkspacesClient] = None
self._sites: typing.Optional[SitesClient] = None
+ self._optimize: typing.Optional[OptimizeClient] = None
self._collections: typing.Optional[CollectionsClient] = None
self._pages: typing.Optional[PagesClient] = None
self._components: typing.Optional[ComponentsClient] = None
self._scripts: typing.Optional[ScriptsClient] = None
self._assets: typing.Optional[AssetsClient] = None
+ self._custom_fonts: typing.Optional[CustomFontsClient] = None
self._webhooks: typing.Optional[WebhooksClient] = None
self._forms: typing.Optional[FormsClient] = None
self._products: typing.Optional[ProductsClient] = None
self._orders: typing.Optional[OrdersClient] = None
self._inventory: typing.Optional[InventoryClient] = None
self._ecommerce: typing.Optional[EcommerceClient] = None
- self._workspaces: typing.Optional[WorkspacesClient] = None
+ self._analyze: typing.Optional[AnalyzeClient] = None
@property
def token(self):
@@ -115,6 +121,14 @@ def token(self):
self._token = TokenClient(client_wrapper=self._client_wrapper)
return self._token
+ @property
+ def workspaces(self):
+ if self._workspaces is None:
+ from .resources.workspaces.client import WorkspacesClient # noqa: E402
+
+ self._workspaces = WorkspacesClient(client_wrapper=self._client_wrapper)
+ return self._workspaces
+
@property
def sites(self):
if self._sites is None:
@@ -123,6 +137,14 @@ def sites(self):
self._sites = SitesClient(client_wrapper=self._client_wrapper)
return self._sites
+ @property
+ def optimize(self):
+ if self._optimize is None:
+ from .resources.optimize.client import OptimizeClient # noqa: E402
+
+ self._optimize = OptimizeClient(client_wrapper=self._client_wrapper)
+ return self._optimize
+
@property
def collections(self):
if self._collections is None:
@@ -163,6 +185,14 @@ def assets(self):
self._assets = AssetsClient(client_wrapper=self._client_wrapper)
return self._assets
+ @property
+ def custom_fonts(self):
+ if self._custom_fonts is None:
+ from .resources.custom_fonts.client import CustomFontsClient # noqa: E402
+
+ self._custom_fonts = CustomFontsClient(client_wrapper=self._client_wrapper)
+ return self._custom_fonts
+
@property
def webhooks(self):
if self._webhooks is None:
@@ -212,12 +242,12 @@ def ecommerce(self):
return self._ecommerce
@property
- def workspaces(self):
- if self._workspaces is None:
- from .resources.workspaces.client import WorkspacesClient # noqa: E402
+ def analyze(self):
+ if self._analyze is None:
+ from .resources.analyze.client import AnalyzeClient # noqa: E402
- self._workspaces = WorkspacesClient(client_wrapper=self._client_wrapper)
- return self._workspaces
+ self._analyze = AnalyzeClient(client_wrapper=self._client_wrapper)
+ return self._analyze
class AsyncWebflow:
@@ -287,19 +317,22 @@ def __init__(
logging=logging,
)
self._token: typing.Optional[AsyncTokenClient] = None
+ self._workspaces: typing.Optional[AsyncWorkspacesClient] = None
self._sites: typing.Optional[AsyncSitesClient] = None
+ self._optimize: typing.Optional[AsyncOptimizeClient] = None
self._collections: typing.Optional[AsyncCollectionsClient] = None
self._pages: typing.Optional[AsyncPagesClient] = None
self._components: typing.Optional[AsyncComponentsClient] = None
self._scripts: typing.Optional[AsyncScriptsClient] = None
self._assets: typing.Optional[AsyncAssetsClient] = None
+ self._custom_fonts: typing.Optional[AsyncCustomFontsClient] = None
self._webhooks: typing.Optional[AsyncWebhooksClient] = None
self._forms: typing.Optional[AsyncFormsClient] = None
self._products: typing.Optional[AsyncProductsClient] = None
self._orders: typing.Optional[AsyncOrdersClient] = None
self._inventory: typing.Optional[AsyncInventoryClient] = None
self._ecommerce: typing.Optional[AsyncEcommerceClient] = None
- self._workspaces: typing.Optional[AsyncWorkspacesClient] = None
+ self._analyze: typing.Optional[AsyncAnalyzeClient] = None
@property
def token(self):
@@ -309,6 +342,14 @@ def token(self):
self._token = AsyncTokenClient(client_wrapper=self._client_wrapper)
return self._token
+ @property
+ def workspaces(self):
+ if self._workspaces is None:
+ from .resources.workspaces.client import AsyncWorkspacesClient # noqa: E402
+
+ self._workspaces = AsyncWorkspacesClient(client_wrapper=self._client_wrapper)
+ return self._workspaces
+
@property
def sites(self):
if self._sites is None:
@@ -317,6 +358,14 @@ def sites(self):
self._sites = AsyncSitesClient(client_wrapper=self._client_wrapper)
return self._sites
+ @property
+ def optimize(self):
+ if self._optimize is None:
+ from .resources.optimize.client import AsyncOptimizeClient # noqa: E402
+
+ self._optimize = AsyncOptimizeClient(client_wrapper=self._client_wrapper)
+ return self._optimize
+
@property
def collections(self):
if self._collections is None:
@@ -357,6 +406,14 @@ def assets(self):
self._assets = AsyncAssetsClient(client_wrapper=self._client_wrapper)
return self._assets
+ @property
+ def custom_fonts(self):
+ if self._custom_fonts is None:
+ from .resources.custom_fonts.client import AsyncCustomFontsClient # noqa: E402
+
+ self._custom_fonts = AsyncCustomFontsClient(client_wrapper=self._client_wrapper)
+ return self._custom_fonts
+
@property
def webhooks(self):
if self._webhooks is None:
@@ -406,9 +463,9 @@ def ecommerce(self):
return self._ecommerce
@property
- def workspaces(self):
- if self._workspaces is None:
- from .resources.workspaces.client import AsyncWorkspacesClient # noqa: E402
+ def analyze(self):
+ if self._analyze is None:
+ from .resources.analyze.client import AsyncAnalyzeClient # noqa: E402
- self._workspaces = AsyncWorkspacesClient(client_wrapper=self._client_wrapper)
- return self._workspaces
+ self._analyze = AsyncAnalyzeClient(client_wrapper=self._client_wrapper)
+ return self._analyze
diff --git a/src/webflow/core/client_wrapper.py b/src/webflow/core/client_wrapper.py
index 7f6db179..38398797 100644
--- a/src/webflow/core/client_wrapper.py
+++ b/src/webflow/core/client_wrapper.py
@@ -28,12 +28,12 @@ def get_headers(self) -> typing.Dict[str, str]:
import platform
headers: typing.Dict[str, str] = {
- "User-Agent": "webflow/2.0.0",
+ "User-Agent": "webflow/2.0.1",
"X-Fern-Language": "Python",
"X-Fern-Runtime": f"python/{platform.python_version()}",
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
"X-Fern-SDK-Name": "webflow",
- "X-Fern-SDK-Version": "2.0.0",
+ "X-Fern-SDK-Version": "2.0.1",
**(self.get_custom_headers() or {}),
}
headers["Authorization"] = f"Bearer {self._get_access_token()}"
diff --git a/src/webflow/errors/__init__.py b/src/webflow/errors/__init__.py
index bd5f6185..14374f19 100644
--- a/src/webflow/errors/__init__.py
+++ b/src/webflow/errors/__init__.py
@@ -11,6 +11,7 @@
from .forbidden_error import ForbiddenError
from .internal_server_error import InternalServerError
from .not_found_error import NotFoundError
+ from .service_unavailable_error import ServiceUnavailableError
from .too_many_requests_error import TooManyRequestsError
from .unauthorized_error import UnauthorizedError
_dynamic_imports: typing.Dict[str, str] = {
@@ -19,6 +20,7 @@
"ForbiddenError": ".forbidden_error",
"InternalServerError": ".internal_server_error",
"NotFoundError": ".not_found_error",
+ "ServiceUnavailableError": ".service_unavailable_error",
"TooManyRequestsError": ".too_many_requests_error",
"UnauthorizedError": ".unauthorized_error",
}
@@ -51,6 +53,7 @@ def __dir__():
"ForbiddenError",
"InternalServerError",
"NotFoundError",
+ "ServiceUnavailableError",
"TooManyRequestsError",
"UnauthorizedError",
]
diff --git a/src/webflow/errors/service_unavailable_error.py b/src/webflow/errors/service_unavailable_error.py
new file mode 100644
index 00000000..50e2d017
--- /dev/null
+++ b/src/webflow/errors/service_unavailable_error.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.api_error import ApiError
+from ..types.error import Error
+
+
+class ServiceUnavailableError(ApiError):
+ def __init__(self, body: Error, headers: typing.Optional[typing.Dict[str, str]] = None):
+ super().__init__(status_code=503, headers=headers, body=body)
diff --git a/src/webflow/resources/__init__.py b/src/webflow/resources/__init__.py
index 7eea2493..ab1794c2 100644
--- a/src/webflow/resources/__init__.py
+++ b/src/webflow/resources/__init__.py
@@ -7,12 +7,15 @@
if typing.TYPE_CHECKING:
from . import (
+ analyze,
assets,
collections,
components,
+ custom_fonts,
ecommerce,
forms,
inventory,
+ optimize,
orders,
pages,
products,
@@ -28,6 +31,15 @@
ComponentsUpdateContentResponse,
ComponentsUpdatePropertiesResponse,
)
+ from .custom_fonts import (
+ CustomFontBatchCreateRequestItemsItem,
+ CustomFontBatchCreateRequestItemsItemFontDisplay,
+ CustomFontBatchDeleteRequestItemsItem,
+ CustomFontsCreateRequestFontDisplay,
+ CustomFontsGetResponse,
+ CustomFontsUpdateRequestFontDisplay,
+ CustomFontsUpdateResponse,
+ )
from .inventory import EcommInventoryChangedPayload, InventoryUpdateRequestInventoryType
from .orders import OrdersListRequestStatus, OrdersRefundRequestReason
from .pages import (
@@ -37,12 +49,19 @@
UpdateStaticContentResponse,
)
from .products import ProductSkuCreateProduct, ProductSkuCreateSku, ProductsCreateSkuResponse
- from .sites import SitesPublishResponse
+ from .sites import SitesPublishResponse, SitesPublishResponsePublishScope
_dynamic_imports: typing.Dict[str, str] = {
"ComponentDomWriteNodesItem": ".components",
"ComponentPropertiesWritePropertiesItem": ".components",
"ComponentsUpdateContentResponse": ".components",
"ComponentsUpdatePropertiesResponse": ".components",
+ "CustomFontBatchCreateRequestItemsItem": ".custom_fonts",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay": ".custom_fonts",
+ "CustomFontBatchDeleteRequestItemsItem": ".custom_fonts",
+ "CustomFontsCreateRequestFontDisplay": ".custom_fonts",
+ "CustomFontsGetResponse": ".custom_fonts",
+ "CustomFontsUpdateRequestFontDisplay": ".custom_fonts",
+ "CustomFontsUpdateResponse": ".custom_fonts",
"EcommInventoryChangedPayload": ".inventory",
"InventoryUpdateRequestInventoryType": ".inventory",
"OrdersListRequestStatus": ".orders",
@@ -54,13 +73,17 @@
"ProductSkuCreateSku": ".products",
"ProductsCreateSkuResponse": ".products",
"SitesPublishResponse": ".sites",
+ "SitesPublishResponsePublishScope": ".sites",
"UpdateStaticContentResponse": ".pages",
+ "analyze": ".analyze",
"assets": ".assets",
"collections": ".collections",
"components": ".components",
+ "custom_fonts": ".custom_fonts",
"ecommerce": ".ecommerce",
"forms": ".forms",
"inventory": ".inventory",
+ "optimize": ".optimize",
"orders": ".orders",
"pages": ".pages",
"products": ".products",
@@ -98,6 +121,13 @@ def __dir__():
"ComponentPropertiesWritePropertiesItem",
"ComponentsUpdateContentResponse",
"ComponentsUpdatePropertiesResponse",
+ "CustomFontBatchCreateRequestItemsItem",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay",
+ "CustomFontBatchDeleteRequestItemsItem",
+ "CustomFontsCreateRequestFontDisplay",
+ "CustomFontsGetResponse",
+ "CustomFontsUpdateRequestFontDisplay",
+ "CustomFontsUpdateResponse",
"EcommInventoryChangedPayload",
"InventoryUpdateRequestInventoryType",
"OrdersListRequestStatus",
@@ -109,13 +139,17 @@ def __dir__():
"ProductSkuCreateSku",
"ProductsCreateSkuResponse",
"SitesPublishResponse",
+ "SitesPublishResponsePublishScope",
"UpdateStaticContentResponse",
+ "analyze",
"assets",
"collections",
"components",
+ "custom_fonts",
"ecommerce",
"forms",
"inventory",
+ "optimize",
"orders",
"pages",
"products",
diff --git a/src/webflow/resources/analyze/__init__.py b/src/webflow/resources/analyze/__init__.py
new file mode 100644
index 00000000..3b6577b9
--- /dev/null
+++ b/src/webflow/resources/analyze/__init__.py
@@ -0,0 +1,70 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .resources import (
+ GoalsGoalPerformanceRequestDeviceType,
+ ReportsBrowserWindowHeightRequestDeviceType,
+ ReportsPageFlowRequestDeviceType,
+ ReportsScrollDepthRequestDeviceType,
+ ReportsTimeOnPageRequestDeviceType,
+ ReportsTopDimensionsRequestDeviceType,
+ ReportsTopEventsRequestDeviceType,
+ ReportsTopPagesRequestDeviceType,
+ ReportsTrafficRequestDeviceType,
+ goals,
+ reports,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "GoalsGoalPerformanceRequestDeviceType": ".resources",
+ "ReportsBrowserWindowHeightRequestDeviceType": ".resources",
+ "ReportsPageFlowRequestDeviceType": ".resources",
+ "ReportsScrollDepthRequestDeviceType": ".resources",
+ "ReportsTimeOnPageRequestDeviceType": ".resources",
+ "ReportsTopDimensionsRequestDeviceType": ".resources",
+ "ReportsTopEventsRequestDeviceType": ".resources",
+ "ReportsTopPagesRequestDeviceType": ".resources",
+ "ReportsTrafficRequestDeviceType": ".resources",
+ "goals": ".resources",
+ "reports": ".resources",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "GoalsGoalPerformanceRequestDeviceType",
+ "ReportsBrowserWindowHeightRequestDeviceType",
+ "ReportsPageFlowRequestDeviceType",
+ "ReportsScrollDepthRequestDeviceType",
+ "ReportsTimeOnPageRequestDeviceType",
+ "ReportsTopDimensionsRequestDeviceType",
+ "ReportsTopEventsRequestDeviceType",
+ "ReportsTopPagesRequestDeviceType",
+ "ReportsTrafficRequestDeviceType",
+ "goals",
+ "reports",
+]
diff --git a/src/webflow/resources/analyze/client.py b/src/webflow/resources/analyze/client.py
new file mode 100644
index 00000000..98ac833c
--- /dev/null
+++ b/src/webflow/resources/analyze/client.py
@@ -0,0 +1,82 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .raw_client import AsyncRawAnalyzeClient, RawAnalyzeClient
+
+if typing.TYPE_CHECKING:
+ from .resources.goals.client import AsyncGoalsClient, GoalsClient
+ from .resources.reports.client import AsyncReportsClient, ReportsClient
+
+
+class AnalyzeClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawAnalyzeClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._reports: typing.Optional[ReportsClient] = None
+ self._goals: typing.Optional[GoalsClient] = None
+
+ @property
+ def with_raw_response(self) -> RawAnalyzeClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawAnalyzeClient
+ """
+ return self._raw_client
+
+ @property
+ def reports(self):
+ if self._reports is None:
+ from .resources.reports.client import ReportsClient # noqa: E402
+
+ self._reports = ReportsClient(client_wrapper=self._client_wrapper)
+ return self._reports
+
+ @property
+ def goals(self):
+ if self._goals is None:
+ from .resources.goals.client import GoalsClient # noqa: E402
+
+ self._goals = GoalsClient(client_wrapper=self._client_wrapper)
+ return self._goals
+
+
+class AsyncAnalyzeClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawAnalyzeClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._reports: typing.Optional[AsyncReportsClient] = None
+ self._goals: typing.Optional[AsyncGoalsClient] = None
+
+ @property
+ def with_raw_response(self) -> AsyncRawAnalyzeClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawAnalyzeClient
+ """
+ return self._raw_client
+
+ @property
+ def reports(self):
+ if self._reports is None:
+ from .resources.reports.client import AsyncReportsClient # noqa: E402
+
+ self._reports = AsyncReportsClient(client_wrapper=self._client_wrapper)
+ return self._reports
+
+ @property
+ def goals(self):
+ if self._goals is None:
+ from .resources.goals.client import AsyncGoalsClient # noqa: E402
+
+ self._goals = AsyncGoalsClient(client_wrapper=self._client_wrapper)
+ return self._goals
diff --git a/src/webflow/resources/analyze/raw_client.py b/src/webflow/resources/analyze/raw_client.py
new file mode 100644
index 00000000..a4bd1051
--- /dev/null
+++ b/src/webflow/resources/analyze/raw_client.py
@@ -0,0 +1,13 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+
+
+class RawAnalyzeClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+
+class AsyncRawAnalyzeClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
diff --git a/src/webflow/resources/analyze/resources/__init__.py b/src/webflow/resources/analyze/resources/__init__.py
new file mode 100644
index 00000000..bce587ff
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/__init__.py
@@ -0,0 +1,69 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from . import goals, reports
+ from .goals import GoalsGoalPerformanceRequestDeviceType
+ from .reports import (
+ ReportsBrowserWindowHeightRequestDeviceType,
+ ReportsPageFlowRequestDeviceType,
+ ReportsScrollDepthRequestDeviceType,
+ ReportsTimeOnPageRequestDeviceType,
+ ReportsTopDimensionsRequestDeviceType,
+ ReportsTopEventsRequestDeviceType,
+ ReportsTopPagesRequestDeviceType,
+ ReportsTrafficRequestDeviceType,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "GoalsGoalPerformanceRequestDeviceType": ".goals",
+ "ReportsBrowserWindowHeightRequestDeviceType": ".reports",
+ "ReportsPageFlowRequestDeviceType": ".reports",
+ "ReportsScrollDepthRequestDeviceType": ".reports",
+ "ReportsTimeOnPageRequestDeviceType": ".reports",
+ "ReportsTopDimensionsRequestDeviceType": ".reports",
+ "ReportsTopEventsRequestDeviceType": ".reports",
+ "ReportsTopPagesRequestDeviceType": ".reports",
+ "ReportsTrafficRequestDeviceType": ".reports",
+ "goals": ".goals",
+ "reports": ".reports",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "GoalsGoalPerformanceRequestDeviceType",
+ "ReportsBrowserWindowHeightRequestDeviceType",
+ "ReportsPageFlowRequestDeviceType",
+ "ReportsScrollDepthRequestDeviceType",
+ "ReportsTimeOnPageRequestDeviceType",
+ "ReportsTopDimensionsRequestDeviceType",
+ "ReportsTopEventsRequestDeviceType",
+ "ReportsTopPagesRequestDeviceType",
+ "ReportsTrafficRequestDeviceType",
+ "goals",
+ "reports",
+]
diff --git a/src/webflow/resources/analyze/resources/goals/__init__.py b/src/webflow/resources/analyze/resources/goals/__init__.py
new file mode 100644
index 00000000..90457894
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/goals/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import GoalsGoalPerformanceRequestDeviceType
+_dynamic_imports: typing.Dict[str, str] = {"GoalsGoalPerformanceRequestDeviceType": ".types"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["GoalsGoalPerformanceRequestDeviceType"]
diff --git a/src/webflow/resources/analyze/resources/goals/client.py b/src/webflow/resources/analyze/resources/goals/client.py
new file mode 100644
index 00000000..4b0c69ed
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/goals/client.py
@@ -0,0 +1,414 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .....types.goal_performance_filter import GoalPerformanceFilter
+from .....types.goal_performance_response import GoalPerformanceResponse
+from .....types.goals_response import GoalsResponse
+from .raw_client import AsyncRawGoalsClient, RawGoalsClient
+from .types.goals_goal_performance_request_device_type import GoalsGoalPerformanceRequestDeviceType
+
+
+class GoalsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawGoalsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawGoalsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawGoalsClient
+ """
+ return self._raw_client
+
+ def list_goals(self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GoalsResponse:
+ """
+ Returns the goals configured for a site. This endpoint returns a list of goals, not a report. It takes no time window, filters, or other query parameters, and unknown query parameters return `400 analyze_input_validation`.
+
+ Each goal includes its type (`goalType`) and whether it's a site goal (`isSiteGoal`). Integration goals also include `integrationSource`, and HubSpot or Marketo form goals include the `formId`. Use a goal's `goalId` with [Get Goal Performance](/data/reference/analyze/goal-performance) to get its daily conversions.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoalsResponse
+ The site's configured goals.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.goals.list_goals(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+ """
+ _response = self._raw_client.list_goals(site_id, request_options=request_options)
+ return _response.data
+
+ def goal_performance(
+ self,
+ site_id: str,
+ goal_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bucket_time_zone: typing.Optional[AnalyzeBucketTimeZone] = None,
+ device_type: typing.Optional[GoalsGoalPerformanceRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[GoalPerformanceFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GoalPerformanceResponse:
+ """
+ Returns daily conversion counts for one goal over a time window, together with the goal's configuration (the same object [List Goals](/data/reference/analyze/list-goals) returns). Conversions are always counted as unique conversions.
+
+ Days are bucketed in `bucketTimeZone`, which defaults to `UTC`. An unknown `goal_id` returns `404`. A goal with no conversions in the window returns an empty `data` array. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ goal_id : str
+ Identifier of the goal, from `goalId` in [List Goals](/data/reference/analyze/list-goals). A numeric string, not a Webflow object ID.
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ bucket_time_zone : typing.Optional[AnalyzeBucketTimeZone]
+ IANA time zone used to align daily bucket boundaries. Defaults to `UTC`.
+
+ device_type : typing.Optional[GoalsGoalPerformanceRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[GoalPerformanceFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `GoalPerformanceFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoalPerformanceResponse
+ The goal's configuration and daily conversion counts for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.goals.goal_performance(
+ site_id="580e63e98c9a982ac9b8b741",
+ goal_id="198020177",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.goal_performance(
+ site_id,
+ goal_id,
+ start_time=start_time,
+ end_time=end_time,
+ bucket_time_zone=bucket_time_zone,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncGoalsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawGoalsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawGoalsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawGoalsClient
+ """
+ return self._raw_client
+
+ async def list_goals(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> GoalsResponse:
+ """
+ Returns the goals configured for a site. This endpoint returns a list of goals, not a report. It takes no time window, filters, or other query parameters, and unknown query parameters return `400 analyze_input_validation`.
+
+ Each goal includes its type (`goalType`) and whether it's a site goal (`isSiteGoal`). Integration goals also include `integrationSource`, and HubSpot or Marketo form goals include the `formId`. Use a goal's `goalId` with [Get Goal Performance](/data/reference/analyze/goal-performance) to get its daily conversions.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoalsResponse
+ The site's configured goals.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.goals.list_goals(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_goals(site_id, request_options=request_options)
+ return _response.data
+
+ async def goal_performance(
+ self,
+ site_id: str,
+ goal_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bucket_time_zone: typing.Optional[AnalyzeBucketTimeZone] = None,
+ device_type: typing.Optional[GoalsGoalPerformanceRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[GoalPerformanceFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GoalPerformanceResponse:
+ """
+ Returns daily conversion counts for one goal over a time window, together with the goal's configuration (the same object [List Goals](/data/reference/analyze/list-goals) returns). Conversions are always counted as unique conversions.
+
+ Days are bucketed in `bucketTimeZone`, which defaults to `UTC`. An unknown `goal_id` returns `404`. A goal with no conversions in the window returns an empty `data` array. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ goal_id : str
+ Identifier of the goal, from `goalId` in [List Goals](/data/reference/analyze/list-goals). A numeric string, not a Webflow object ID.
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ bucket_time_zone : typing.Optional[AnalyzeBucketTimeZone]
+ IANA time zone used to align daily bucket boundaries. Defaults to `UTC`.
+
+ device_type : typing.Optional[GoalsGoalPerformanceRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[GoalPerformanceFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `GoalPerformanceFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoalPerformanceResponse
+ The goal's configuration and daily conversion counts for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.goals.goal_performance(
+ site_id="580e63e98c9a982ac9b8b741",
+ goal_id="198020177",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.goal_performance(
+ site_id,
+ goal_id,
+ start_time=start_time,
+ end_time=end_time,
+ bucket_time_zone=bucket_time_zone,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/analyze/resources/goals/raw_client.py b/src/webflow/resources/analyze/resources/goals/raw_client.py
new file mode 100644
index 00000000..0faf9f98
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/goals/raw_client.py
@@ -0,0 +1,664 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.datetime_utils import serialize_datetime
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....core.serialization import convert_and_respect_annotation_metadata
+from .....errors.bad_request_error import BadRequestError
+from .....errors.forbidden_error import ForbiddenError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .....types.error import Error
+from .....types.goal_performance_filter import GoalPerformanceFilter
+from .....types.goal_performance_response import GoalPerformanceResponse
+from .....types.goals_response import GoalsResponse
+from .types.goals_goal_performance_request_device_type import GoalsGoalPerformanceRequestDeviceType
+from pydantic import ValidationError
+
+
+class RawGoalsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list_goals(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[GoalsResponse]:
+ """
+ Returns the goals configured for a site. This endpoint returns a list of goals, not a report. It takes no time window, filters, or other query parameters, and unknown query parameters return `400 analyze_input_validation`.
+
+ Each goal includes its type (`goalType`) and whether it's a site goal (`isSiteGoal`). Integration goals also include `integrationSource`, and HubSpot or Marketo form goals include the `formId`. Use a goal's `goalId` with [Get Goal Performance](/data/reference/analyze/goal-performance) to get its daily conversions.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GoalsResponse]
+ The site's configured goals.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/goals",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoalsResponse,
+ parse_obj_as(
+ type_=GoalsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def goal_performance(
+ self,
+ site_id: str,
+ goal_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bucket_time_zone: typing.Optional[AnalyzeBucketTimeZone] = None,
+ device_type: typing.Optional[GoalsGoalPerformanceRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[GoalPerformanceFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[GoalPerformanceResponse]:
+ """
+ Returns daily conversion counts for one goal over a time window, together with the goal's configuration (the same object [List Goals](/data/reference/analyze/list-goals) returns). Conversions are always counted as unique conversions.
+
+ Days are bucketed in `bucketTimeZone`, which defaults to `UTC`. An unknown `goal_id` returns `404`. A goal with no conversions in the window returns an empty `data` array. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ goal_id : str
+ Identifier of the goal, from `goalId` in [List Goals](/data/reference/analyze/list-goals). A numeric string, not a Webflow object ID.
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ bucket_time_zone : typing.Optional[AnalyzeBucketTimeZone]
+ IANA time zone used to align daily bucket boundaries. Defaults to `UTC`.
+
+ device_type : typing.Optional[GoalsGoalPerformanceRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[GoalPerformanceFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `GoalPerformanceFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GoalPerformanceResponse]
+ The goal's configuration and daily conversion counts for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/goals/{jsonable_encoder(goal_id)}/performance",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "bucketTimeZone": bucket_time_zone,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=GoalPerformanceFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoalPerformanceResponse,
+ parse_obj_as(
+ type_=GoalPerformanceResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawGoalsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list_goals(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[GoalsResponse]:
+ """
+ Returns the goals configured for a site. This endpoint returns a list of goals, not a report. It takes no time window, filters, or other query parameters, and unknown query parameters return `400 analyze_input_validation`.
+
+ Each goal includes its type (`goalType`) and whether it's a site goal (`isSiteGoal`). Integration goals also include `integrationSource`, and HubSpot or Marketo form goals include the `formId`. Use a goal's `goalId` with [Get Goal Performance](/data/reference/analyze/goal-performance) to get its daily conversions.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GoalsResponse]
+ The site's configured goals.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/goals",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoalsResponse,
+ parse_obj_as(
+ type_=GoalsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def goal_performance(
+ self,
+ site_id: str,
+ goal_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bucket_time_zone: typing.Optional[AnalyzeBucketTimeZone] = None,
+ device_type: typing.Optional[GoalsGoalPerformanceRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[GoalPerformanceFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[GoalPerformanceResponse]:
+ """
+ Returns daily conversion counts for one goal over a time window, together with the goal's configuration (the same object [List Goals](/data/reference/analyze/list-goals) returns). Conversions are always counted as unique conversions.
+
+ Days are bucketed in `bucketTimeZone`, which defaults to `UTC`. An unknown `goal_id` returns `404`. A goal with no conversions in the window returns an empty `data` array. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ goal_id : str
+ Identifier of the goal, from `goalId` in [List Goals](/data/reference/analyze/list-goals). A numeric string, not a Webflow object ID.
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ bucket_time_zone : typing.Optional[AnalyzeBucketTimeZone]
+ IANA time zone used to align daily bucket boundaries. Defaults to `UTC`.
+
+ device_type : typing.Optional[GoalsGoalPerformanceRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[GoalPerformanceFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `GoalPerformanceFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GoalPerformanceResponse]
+ The goal's configuration and daily conversion counts for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/goals/{jsonable_encoder(goal_id)}/performance",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "bucketTimeZone": bucket_time_zone,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=GoalPerformanceFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoalPerformanceResponse,
+ parse_obj_as(
+ type_=GoalPerformanceResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/analyze/resources/goals/types/__init__.py b/src/webflow/resources/analyze/resources/goals/types/__init__.py
new file mode 100644
index 00000000..409d018d
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/goals/types/__init__.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .goals_goal_performance_request_device_type import GoalsGoalPerformanceRequestDeviceType
+_dynamic_imports: typing.Dict[str, str] = {
+ "GoalsGoalPerformanceRequestDeviceType": ".goals_goal_performance_request_device_type"
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["GoalsGoalPerformanceRequestDeviceType"]
diff --git a/src/webflow/resources/analyze/resources/goals/types/goals_goal_performance_request_device_type.py b/src/webflow/resources/analyze/resources/goals/types/goals_goal_performance_request_device_type.py
new file mode 100644
index 00000000..9348be7b
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/goals/types/goals_goal_performance_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalsGoalPerformanceRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/__init__.py b/src/webflow/resources/analyze/resources/reports/__init__.py
new file mode 100644
index 00000000..f6c66040
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/__init__.py
@@ -0,0 +1,61 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ ReportsBrowserWindowHeightRequestDeviceType,
+ ReportsPageFlowRequestDeviceType,
+ ReportsScrollDepthRequestDeviceType,
+ ReportsTimeOnPageRequestDeviceType,
+ ReportsTopDimensionsRequestDeviceType,
+ ReportsTopEventsRequestDeviceType,
+ ReportsTopPagesRequestDeviceType,
+ ReportsTrafficRequestDeviceType,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "ReportsBrowserWindowHeightRequestDeviceType": ".types",
+ "ReportsPageFlowRequestDeviceType": ".types",
+ "ReportsScrollDepthRequestDeviceType": ".types",
+ "ReportsTimeOnPageRequestDeviceType": ".types",
+ "ReportsTopDimensionsRequestDeviceType": ".types",
+ "ReportsTopEventsRequestDeviceType": ".types",
+ "ReportsTopPagesRequestDeviceType": ".types",
+ "ReportsTrafficRequestDeviceType": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ReportsBrowserWindowHeightRequestDeviceType",
+ "ReportsPageFlowRequestDeviceType",
+ "ReportsScrollDepthRequestDeviceType",
+ "ReportsTimeOnPageRequestDeviceType",
+ "ReportsTopDimensionsRequestDeviceType",
+ "ReportsTopEventsRequestDeviceType",
+ "ReportsTopPagesRequestDeviceType",
+ "ReportsTrafficRequestDeviceType",
+]
diff --git a/src/webflow/resources/analyze/resources/reports/client.py b/src/webflow/resources/analyze/resources/reports/client.py
new file mode 100644
index 00000000..70b61d4c
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/client.py
@@ -0,0 +1,2336 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .....types.analyze_daily_timeseries_query import AnalyzeDailyTimeseriesQuery
+from .....types.browser_window_height_filter import BrowserWindowHeightFilter
+from .....types.browser_window_height_response import BrowserWindowHeightResponse
+from .....types.page_flow_direction import PageFlowDirection
+from .....types.page_flow_filter import PageFlowFilter
+from .....types.page_flow_metric_scope import PageFlowMetricScope
+from .....types.page_flow_response import PageFlowResponse
+from .....types.scroll_depth_filter import ScrollDepthFilter
+from .....types.scroll_depth_response import ScrollDepthResponse
+from .....types.time_on_page_filter import TimeOnPageFilter
+from .....types.time_on_page_metric_scope import TimeOnPageMetricScope
+from .....types.time_on_page_response import TimeOnPageResponse
+from .....types.time_on_page_timeseries_query import TimeOnPageTimeseriesQuery
+from .....types.top_dimensions_dimension import TopDimensionsDimension
+from .....types.top_dimensions_filter import TopDimensionsFilter
+from .....types.top_dimensions_metric_scope import TopDimensionsMetricScope
+from .....types.top_dimensions_response import TopDimensionsResponse
+from .....types.top_events_filter import TopEventsFilter
+from .....types.top_events_response import TopEventsResponse
+from .....types.top_pages_filter import TopPagesFilter
+from .....types.top_pages_response import TopPagesResponse
+from .....types.top_pages_sort_by import TopPagesSortBy
+from .....types.traffic_filter import TrafficFilter
+from .....types.traffic_metric_scope import TrafficMetricScope
+from .....types.traffic_response import TrafficResponse
+from .raw_client import AsyncRawReportsClient, RawReportsClient
+from .types.reports_browser_window_height_request_device_type import ReportsBrowserWindowHeightRequestDeviceType
+from .types.reports_page_flow_request_device_type import ReportsPageFlowRequestDeviceType
+from .types.reports_scroll_depth_request_device_type import ReportsScrollDepthRequestDeviceType
+from .types.reports_time_on_page_request_device_type import ReportsTimeOnPageRequestDeviceType
+from .types.reports_top_dimensions_request_device_type import ReportsTopDimensionsRequestDeviceType
+from .types.reports_top_events_request_device_type import ReportsTopEventsRequestDeviceType
+from .types.reports_top_pages_request_device_type import ReportsTopPagesRequestDeviceType
+from .types.reports_traffic_request_device_type import ReportsTrafficRequestDeviceType
+
+
+class ReportsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawReportsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawReportsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawReportsClient
+ """
+ return self._raw_client
+
+ def traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TrafficMetricScope,
+ bucket_time_zone: AnalyzeBucketTimeZone,
+ device_type: typing.Optional[ReportsTrafficRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TrafficFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TrafficResponse:
+ """
+ Returns a daily time series of a single metric — sessions, users, or page views — over a time window.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TrafficMetricScope
+ The unit each `count` data point is measured in.
+
+ bucket_time_zone : AnalyzeBucketTimeZone
+ IANA time zone used to align daily bucket boundaries.
+
+ device_type : typing.Optional[ReportsTrafficRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TrafficFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TrafficFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TrafficResponse
+ Time-series traffic report for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.traffic(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ metric_scope="session",
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.traffic(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ metric_scope=metric_scope,
+ bucket_time_zone=bucket_time_zone,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def top_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ sort_by: typing.Optional[TopPagesSortBy] = None,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopPagesRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopPagesFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopPagesResponse:
+ """
+ Returns the most-visited pages over a time window, ranked by `sortBy` (sessions, users, or page views).
+
+ Each row carries all three scope counts; `sortBy` only governs ordering. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily page view `timeseries` to each row. Bucket counts are always page views regardless of `sortBy`. Row-level counts honor the requested sort, but the timeseries does not.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ sort_by : typing.Optional[TopPagesSortBy]
+ Metric used to rank rows in the response, descending. Defaults to `session`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily page view `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-page timeseries data.
+
+ device_type : typing.Optional[ReportsTopPagesRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopPagesFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopPagesFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopPagesResponse
+ Pages ranked by `sortBy` for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.top_pages(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ sort_by="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.top_pages(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ sort_by=sort_by,
+ limit=limit,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def top_dimensions(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ dimension: TopDimensionsDimension,
+ metric_scope: TopDimensionsMetricScope,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsTopDimensionsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopDimensionsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopDimensionsResponse:
+ """
+ Returns the top values within a chosen `dimension` — top countries, top traffic sources, top campaigns, top audiences, and so on — over a time window, ranked by sessions or users.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ dimension : TopDimensionsDimension
+ The dimension whose top values are ranked. See `TopDimensionsDimension` for the supported values.
+
+ metric_scope : TopDimensionsMetricScope
+ The unit each row's `count` is measured in — sessions or users.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsTopDimensionsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopDimensionsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopDimensionsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopDimensionsResponse
+ Dimension values ranked by `count` for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.top_dimensions(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ dimension="country",
+ metric_scope="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.top_dimensions(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ dimension=dimension,
+ metric_scope=metric_scope,
+ limit=limit,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def top_events(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopEventsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopEventsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopEventsResponse:
+ """
+ Returns the top events over a time window, ranked by how often they occurred.
+
+ Events are counted individually, not rolled up into sessions, users, or page views — so this report has no `metricScope`. Each row's `count` is how many times the event occurred. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily event count `timeseries` to each row.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily event count `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-event timeseries data.
+
+ device_type : typing.Optional[ReportsTopEventsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopEventsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopEventsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopEventsResponse
+ Events ranked by how frequently they occurred, for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.top_events(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.top_events(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ limit=limit,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def time_on_page(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TimeOnPageMetricScope,
+ timeseries: typing.Optional[TimeOnPageTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTimeOnPageRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TimeOnPageFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TimeOnPageResponse:
+ """
+ Returns the average time on page over a time window — as a single aggregate value, or bucketed by day or week when `timeseries` is supplied.
+
+ Choose how the average is computed with `metricScope` (per session, user, or page view). Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TimeOnPageMetricScope
+ How the average time on page is computed — per session, user, or page view.
+
+ timeseries : typing.Optional[TimeOnPageTimeseriesQuery]
+ Include bucketed average time data using the supplied granularity and IANA time zone. Omit this parameter to return a single aggregate value for the requested window.
+
+ device_type : typing.Optional[ReportsTimeOnPageRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TimeOnPageFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TimeOnPageFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TimeOnPageResponse
+ Average time on page for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.time_on_page(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ metric_scope="session",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.time_on_page(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ metric_scope=metric_scope,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def scroll_depth(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsScrollDepthRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[ScrollDepthFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ScrollDepthResponse:
+ """
+ Returns the scroll-depth distribution for the site over a time window. Each row reports the scroll depth (in pixels) that a share of page views reached, at fixed breakpoints of 75%, 50%, and 25% of page views.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsScrollDepthRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[ScrollDepthFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `ScrollDepthFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ScrollDepthResponse
+ Scroll-depth distribution for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.scroll_depth(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.scroll_depth(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def browser_window_height(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsBrowserWindowHeightRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[BrowserWindowHeightFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> BrowserWindowHeightResponse:
+ """
+ Returns the average browser window height (in CSS pixels) for the site over a time window. The response wraps the single aggregate in a one-element `data` array for SDK uniformity with other Analyze reports.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsBrowserWindowHeightRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[BrowserWindowHeightFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `BrowserWindowHeightFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BrowserWindowHeightResponse
+ Average browser window height for the requested window and filters.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.browser_window_height(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.browser_window_height(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def page_flow(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ direction: PageFlowDirection,
+ metric_scope: PageFlowMetricScope,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsPageFlowRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[PageFlowFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> PageFlowResponse:
+ """
+ Returns the pages visitors viewed immediately before or after a required anchor page over a time window, ranked by sessions, users, or page views.
+
+ For a CMS template instance, provide both `collectionId` and `itemSlug` with `pageId`. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ direction : PageFlowDirection
+ Whether to return pages viewed immediately before or after the anchor page.
+
+ metric_scope : PageFlowMetricScope
+ The unit each row's `count` is measured in.
+
+ page_id : str
+ Identifier of the anchor Webflow page.
+
+ collection_id : typing.Optional[str]
+ Identifier of the CMS collection for an anchor template-page instance. Must be provided together with `itemSlug`, or both must be omitted.
+
+ item_slug : typing.Optional[str]
+ Slug of the CMS item for an anchor template-page instance. Must be provided together with `collectionId`, or both must be omitted.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `10`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsPageFlowRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[PageFlowFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `PageFlowFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ PageFlowResponse
+ Pages viewed immediately before or after the anchor page, ranked by `count`.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.analyze.reports.page_flow(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ direction="previous",
+ metric_scope="session",
+ page_id="65f1b2c4a8d3e5f7a9c1b2d3",
+ collection_id="66abc1234567890abcdef012",
+ item_slug="anchor-post",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+ """
+ _response = self._raw_client.page_flow(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ direction=direction,
+ metric_scope=metric_scope,
+ page_id=page_id,
+ collection_id=collection_id,
+ item_slug=item_slug,
+ limit=limit,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncReportsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawReportsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawReportsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawReportsClient
+ """
+ return self._raw_client
+
+ async def traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TrafficMetricScope,
+ bucket_time_zone: AnalyzeBucketTimeZone,
+ device_type: typing.Optional[ReportsTrafficRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TrafficFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TrafficResponse:
+ """
+ Returns a daily time series of a single metric — sessions, users, or page views — over a time window.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TrafficMetricScope
+ The unit each `count` data point is measured in.
+
+ bucket_time_zone : AnalyzeBucketTimeZone
+ IANA time zone used to align daily bucket boundaries.
+
+ device_type : typing.Optional[ReportsTrafficRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TrafficFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TrafficFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TrafficResponse
+ Time-series traffic report for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.traffic(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ metric_scope="session",
+ bucket_time_zone="America/New_York",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.traffic(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ metric_scope=metric_scope,
+ bucket_time_zone=bucket_time_zone,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def top_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ sort_by: typing.Optional[TopPagesSortBy] = None,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopPagesRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopPagesFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopPagesResponse:
+ """
+ Returns the most-visited pages over a time window, ranked by `sortBy` (sessions, users, or page views).
+
+ Each row carries all three scope counts; `sortBy` only governs ordering. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily page view `timeseries` to each row. Bucket counts are always page views regardless of `sortBy`. Row-level counts honor the requested sort, but the timeseries does not.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ sort_by : typing.Optional[TopPagesSortBy]
+ Metric used to rank rows in the response, descending. Defaults to `session`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily page view `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-page timeseries data.
+
+ device_type : typing.Optional[ReportsTopPagesRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopPagesFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopPagesFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopPagesResponse
+ Pages ranked by `sortBy` for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.top_pages(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ sort_by="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.top_pages(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ sort_by=sort_by,
+ limit=limit,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def top_dimensions(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ dimension: TopDimensionsDimension,
+ metric_scope: TopDimensionsMetricScope,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsTopDimensionsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopDimensionsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopDimensionsResponse:
+ """
+ Returns the top values within a chosen `dimension` — top countries, top traffic sources, top campaigns, top audiences, and so on — over a time window, ranked by sessions or users.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ dimension : TopDimensionsDimension
+ The dimension whose top values are ranked. See `TopDimensionsDimension` for the supported values.
+
+ metric_scope : TopDimensionsMetricScope
+ The unit each row's `count` is measured in — sessions or users.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsTopDimensionsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopDimensionsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopDimensionsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopDimensionsResponse
+ Dimension values ranked by `count` for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.top_dimensions(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ dimension="country",
+ metric_scope="session",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.top_dimensions(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ dimension=dimension,
+ metric_scope=metric_scope,
+ limit=limit,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def top_events(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopEventsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopEventsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TopEventsResponse:
+ """
+ Returns the top events over a time window, ranked by how often they occurred.
+
+ Events are counted individually, not rolled up into sessions, users, or page views — so this report has no `metricScope`. Each row's `count` is how many times the event occurred. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily event count `timeseries` to each row.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily event count `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-event timeseries data.
+
+ device_type : typing.Optional[ReportsTopEventsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopEventsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopEventsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TopEventsResponse
+ Events ranked by how frequently they occurred, for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.top_events(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.top_events(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ limit=limit,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def time_on_page(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TimeOnPageMetricScope,
+ timeseries: typing.Optional[TimeOnPageTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTimeOnPageRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TimeOnPageFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> TimeOnPageResponse:
+ """
+ Returns the average time on page over a time window — as a single aggregate value, or bucketed by day or week when `timeseries` is supplied.
+
+ Choose how the average is computed with `metricScope` (per session, user, or page view). Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TimeOnPageMetricScope
+ How the average time on page is computed — per session, user, or page view.
+
+ timeseries : typing.Optional[TimeOnPageTimeseriesQuery]
+ Include bucketed average time data using the supplied granularity and IANA time zone. Omit this parameter to return a single aggregate value for the requested window.
+
+ device_type : typing.Optional[ReportsTimeOnPageRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TimeOnPageFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TimeOnPageFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ TimeOnPageResponse
+ Average time on page for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.time_on_page(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ metric_scope="session",
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.time_on_page(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ metric_scope=metric_scope,
+ timeseries=timeseries,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def scroll_depth(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsScrollDepthRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[ScrollDepthFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ScrollDepthResponse:
+ """
+ Returns the scroll-depth distribution for the site over a time window. Each row reports the scroll depth (in pixels) that a share of page views reached, at fixed breakpoints of 75%, 50%, and 25% of page views.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsScrollDepthRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[ScrollDepthFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `ScrollDepthFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ScrollDepthResponse
+ Scroll-depth distribution for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.scroll_depth(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.scroll_depth(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def browser_window_height(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsBrowserWindowHeightRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[BrowserWindowHeightFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> BrowserWindowHeightResponse:
+ """
+ Returns the average browser window height (in CSS pixels) for the site over a time window. The response wraps the single aggregate in a one-element `data` array for SDK uniformity with other Analyze reports.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsBrowserWindowHeightRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[BrowserWindowHeightFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `BrowserWindowHeightFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ BrowserWindowHeightResponse
+ Average browser window height for the requested window and filters.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.browser_window_height(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.browser_window_height(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def page_flow(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ direction: PageFlowDirection,
+ metric_scope: PageFlowMetricScope,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsPageFlowRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[PageFlowFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> PageFlowResponse:
+ """
+ Returns the pages visitors viewed immediately before or after a required anchor page over a time window, ranked by sessions, users, or page views.
+
+ For a CMS template instance, provide both `collectionId` and `itemSlug` with `pageId`. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ direction : PageFlowDirection
+ Whether to return pages viewed immediately before or after the anchor page.
+
+ metric_scope : PageFlowMetricScope
+ The unit each row's `count` is measured in.
+
+ page_id : str
+ Identifier of the anchor Webflow page.
+
+ collection_id : typing.Optional[str]
+ Identifier of the CMS collection for an anchor template-page instance. Must be provided together with `itemSlug`, or both must be omitted.
+
+ item_slug : typing.Optional[str]
+ Slug of the CMS item for an anchor template-page instance. Must be provided together with `collectionId`, or both must be omitted.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `10`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsPageFlowRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[PageFlowFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `PageFlowFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ PageFlowResponse
+ Pages viewed immediately before or after the anchor page, ranked by `count`.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.analyze.reports.page_flow(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-04-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-04-08 00:00:00+00:00",
+ ),
+ direction="previous",
+ metric_scope="session",
+ page_id="65f1b2c4a8d3e5f7a9c1b2d3",
+ collection_id="66abc1234567890abcdef012",
+ item_slug="anchor-post",
+ limit=1,
+ device_type="desktop",
+ country="US",
+ page_path="/towels",
+ traffic_source="SO",
+ referrer="google.com",
+ browser="Chrome",
+ utm_campaign="dont-panic-2026",
+ utm_medium="email",
+ utm_source="hitchhikers-guide",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.page_flow(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ direction=direction,
+ metric_scope=metric_scope,
+ page_id=page_id,
+ collection_id=collection_id,
+ item_slug=item_slug,
+ limit=limit,
+ device_type=device_type,
+ country=country,
+ page_path=page_path,
+ traffic_source=traffic_source,
+ referrer=referrer,
+ browser=browser,
+ utm_campaign=utm_campaign,
+ utm_medium=utm_medium,
+ utm_source=utm_source,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/analyze/resources/reports/raw_client.py b/src/webflow/resources/analyze/resources/reports/raw_client.py
new file mode 100644
index 00000000..3f6e3a03
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/raw_client.py
@@ -0,0 +1,3233 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.datetime_utils import serialize_datetime
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....core.serialization import convert_and_respect_annotation_metadata
+from .....errors.bad_request_error import BadRequestError
+from .....errors.forbidden_error import ForbiddenError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .....types.analyze_daily_timeseries_query import AnalyzeDailyTimeseriesQuery
+from .....types.browser_window_height_filter import BrowserWindowHeightFilter
+from .....types.browser_window_height_response import BrowserWindowHeightResponse
+from .....types.error import Error
+from .....types.page_flow_direction import PageFlowDirection
+from .....types.page_flow_filter import PageFlowFilter
+from .....types.page_flow_metric_scope import PageFlowMetricScope
+from .....types.page_flow_response import PageFlowResponse
+from .....types.scroll_depth_filter import ScrollDepthFilter
+from .....types.scroll_depth_response import ScrollDepthResponse
+from .....types.time_on_page_filter import TimeOnPageFilter
+from .....types.time_on_page_metric_scope import TimeOnPageMetricScope
+from .....types.time_on_page_response import TimeOnPageResponse
+from .....types.time_on_page_timeseries_query import TimeOnPageTimeseriesQuery
+from .....types.top_dimensions_dimension import TopDimensionsDimension
+from .....types.top_dimensions_filter import TopDimensionsFilter
+from .....types.top_dimensions_metric_scope import TopDimensionsMetricScope
+from .....types.top_dimensions_response import TopDimensionsResponse
+from .....types.top_events_filter import TopEventsFilter
+from .....types.top_events_response import TopEventsResponse
+from .....types.top_pages_filter import TopPagesFilter
+from .....types.top_pages_response import TopPagesResponse
+from .....types.top_pages_sort_by import TopPagesSortBy
+from .....types.traffic_filter import TrafficFilter
+from .....types.traffic_metric_scope import TrafficMetricScope
+from .....types.traffic_response import TrafficResponse
+from .types.reports_browser_window_height_request_device_type import ReportsBrowserWindowHeightRequestDeviceType
+from .types.reports_page_flow_request_device_type import ReportsPageFlowRequestDeviceType
+from .types.reports_scroll_depth_request_device_type import ReportsScrollDepthRequestDeviceType
+from .types.reports_time_on_page_request_device_type import ReportsTimeOnPageRequestDeviceType
+from .types.reports_top_dimensions_request_device_type import ReportsTopDimensionsRequestDeviceType
+from .types.reports_top_events_request_device_type import ReportsTopEventsRequestDeviceType
+from .types.reports_top_pages_request_device_type import ReportsTopPagesRequestDeviceType
+from .types.reports_traffic_request_device_type import ReportsTrafficRequestDeviceType
+from pydantic import ValidationError
+
+
+class RawReportsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TrafficMetricScope,
+ bucket_time_zone: AnalyzeBucketTimeZone,
+ device_type: typing.Optional[ReportsTrafficRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TrafficFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[TrafficResponse]:
+ """
+ Returns a daily time series of a single metric — sessions, users, or page views — over a time window.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TrafficMetricScope
+ The unit each `count` data point is measured in.
+
+ bucket_time_zone : AnalyzeBucketTimeZone
+ IANA time zone used to align daily bucket boundaries.
+
+ device_type : typing.Optional[ReportsTrafficRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TrafficFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TrafficFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[TrafficResponse]
+ Time-series traffic report for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/traffic",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "metricScope": metric_scope,
+ "bucketTimeZone": bucket_time_zone,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TrafficFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TrafficResponse,
+ parse_obj_as(
+ type_=TrafficResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def top_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ sort_by: typing.Optional[TopPagesSortBy] = None,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopPagesRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopPagesFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[TopPagesResponse]:
+ """
+ Returns the most-visited pages over a time window, ranked by `sortBy` (sessions, users, or page views).
+
+ Each row carries all three scope counts; `sortBy` only governs ordering. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily page view `timeseries` to each row. Bucket counts are always page views regardless of `sortBy`. Row-level counts honor the requested sort, but the timeseries does not.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ sort_by : typing.Optional[TopPagesSortBy]
+ Metric used to rank rows in the response, descending. Defaults to `session`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily page view `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-page timeseries data.
+
+ device_type : typing.Optional[ReportsTopPagesRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopPagesFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopPagesFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[TopPagesResponse]
+ Pages ranked by `sortBy` for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_pages",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "sortBy": sort_by,
+ "limit": limit,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AnalyzeDailyTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopPagesFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopPagesResponse,
+ parse_obj_as(
+ type_=TopPagesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def top_dimensions(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ dimension: TopDimensionsDimension,
+ metric_scope: TopDimensionsMetricScope,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsTopDimensionsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopDimensionsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[TopDimensionsResponse]:
+ """
+ Returns the top values within a chosen `dimension` — top countries, top traffic sources, top campaigns, top audiences, and so on — over a time window, ranked by sessions or users.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ dimension : TopDimensionsDimension
+ The dimension whose top values are ranked. See `TopDimensionsDimension` for the supported values.
+
+ metric_scope : TopDimensionsMetricScope
+ The unit each row's `count` is measured in — sessions or users.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsTopDimensionsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopDimensionsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopDimensionsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[TopDimensionsResponse]
+ Dimension values ranked by `count` for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_dimensions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "dimension": dimension,
+ "metricScope": metric_scope,
+ "limit": limit,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopDimensionsFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopDimensionsResponse,
+ parse_obj_as(
+ type_=TopDimensionsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def top_events(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopEventsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopEventsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[TopEventsResponse]:
+ """
+ Returns the top events over a time window, ranked by how often they occurred.
+
+ Events are counted individually, not rolled up into sessions, users, or page views — so this report has no `metricScope`. Each row's `count` is how many times the event occurred. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily event count `timeseries` to each row.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily event count `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-event timeseries data.
+
+ device_type : typing.Optional[ReportsTopEventsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopEventsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopEventsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[TopEventsResponse]
+ Events ranked by how frequently they occurred, for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_events",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "limit": limit,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AnalyzeDailyTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopEventsFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopEventsResponse,
+ parse_obj_as(
+ type_=TopEventsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def time_on_page(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TimeOnPageMetricScope,
+ timeseries: typing.Optional[TimeOnPageTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTimeOnPageRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TimeOnPageFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[TimeOnPageResponse]:
+ """
+ Returns the average time on page over a time window — as a single aggregate value, or bucketed by day or week when `timeseries` is supplied.
+
+ Choose how the average is computed with `metricScope` (per session, user, or page view). Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TimeOnPageMetricScope
+ How the average time on page is computed — per session, user, or page view.
+
+ timeseries : typing.Optional[TimeOnPageTimeseriesQuery]
+ Include bucketed average time data using the supplied granularity and IANA time zone. Omit this parameter to return a single aggregate value for the requested window.
+
+ device_type : typing.Optional[ReportsTimeOnPageRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TimeOnPageFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TimeOnPageFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[TimeOnPageResponse]
+ Average time on page for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/time_on_page",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "metricScope": metric_scope,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=TimeOnPageTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TimeOnPageFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TimeOnPageResponse,
+ parse_obj_as(
+ type_=TimeOnPageResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def scroll_depth(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsScrollDepthRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[ScrollDepthFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ScrollDepthResponse]:
+ """
+ Returns the scroll-depth distribution for the site over a time window. Each row reports the scroll depth (in pixels) that a share of page views reached, at fixed breakpoints of 75%, 50%, and 25% of page views.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsScrollDepthRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[ScrollDepthFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `ScrollDepthFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ScrollDepthResponse]
+ Scroll-depth distribution for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/scroll_depth",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=ScrollDepthFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ScrollDepthResponse,
+ parse_obj_as(
+ type_=ScrollDepthResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def browser_window_height(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsBrowserWindowHeightRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[BrowserWindowHeightFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[BrowserWindowHeightResponse]:
+ """
+ Returns the average browser window height (in CSS pixels) for the site over a time window. The response wraps the single aggregate in a one-element `data` array for SDK uniformity with other Analyze reports.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsBrowserWindowHeightRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[BrowserWindowHeightFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `BrowserWindowHeightFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[BrowserWindowHeightResponse]
+ Average browser window height for the requested window and filters.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/browser_window_height",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=BrowserWindowHeightFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BrowserWindowHeightResponse,
+ parse_obj_as(
+ type_=BrowserWindowHeightResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def page_flow(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ direction: PageFlowDirection,
+ metric_scope: PageFlowMetricScope,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsPageFlowRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[PageFlowFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[PageFlowResponse]:
+ """
+ Returns the pages visitors viewed immediately before or after a required anchor page over a time window, ranked by sessions, users, or page views.
+
+ For a CMS template instance, provide both `collectionId` and `itemSlug` with `pageId`. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ direction : PageFlowDirection
+ Whether to return pages viewed immediately before or after the anchor page.
+
+ metric_scope : PageFlowMetricScope
+ The unit each row's `count` is measured in.
+
+ page_id : str
+ Identifier of the anchor Webflow page.
+
+ collection_id : typing.Optional[str]
+ Identifier of the CMS collection for an anchor template-page instance. Must be provided together with `itemSlug`, or both must be omitted.
+
+ item_slug : typing.Optional[str]
+ Slug of the CMS item for an anchor template-page instance. Must be provided together with `collectionId`, or both must be omitted.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `10`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsPageFlowRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[PageFlowFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `PageFlowFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[PageFlowResponse]
+ Pages viewed immediately before or after the anchor page, ranked by `count`.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/page_flow",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "direction": direction,
+ "metricScope": metric_scope,
+ "pageId": page_id,
+ "collectionId": collection_id,
+ "itemSlug": item_slug,
+ "limit": limit,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=PageFlowFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ PageFlowResponse,
+ parse_obj_as(
+ type_=PageFlowResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawReportsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TrafficMetricScope,
+ bucket_time_zone: AnalyzeBucketTimeZone,
+ device_type: typing.Optional[ReportsTrafficRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TrafficFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[TrafficResponse]:
+ """
+ Returns a daily time series of a single metric — sessions, users, or page views — over a time window.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TrafficMetricScope
+ The unit each `count` data point is measured in.
+
+ bucket_time_zone : AnalyzeBucketTimeZone
+ IANA time zone used to align daily bucket boundaries.
+
+ device_type : typing.Optional[ReportsTrafficRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TrafficFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TrafficFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[TrafficResponse]
+ Time-series traffic report for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/traffic",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "metricScope": metric_scope,
+ "bucketTimeZone": bucket_time_zone,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TrafficFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TrafficResponse,
+ parse_obj_as(
+ type_=TrafficResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def top_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ sort_by: typing.Optional[TopPagesSortBy] = None,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopPagesRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopPagesFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[TopPagesResponse]:
+ """
+ Returns the most-visited pages over a time window, ranked by `sortBy` (sessions, users, or page views).
+
+ Each row carries all three scope counts; `sortBy` only governs ordering. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily page view `timeseries` to each row. Bucket counts are always page views regardless of `sortBy`. Row-level counts honor the requested sort, but the timeseries does not.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ sort_by : typing.Optional[TopPagesSortBy]
+ Metric used to rank rows in the response, descending. Defaults to `session`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily page view `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-page timeseries data.
+
+ device_type : typing.Optional[ReportsTopPagesRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopPagesFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopPagesFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[TopPagesResponse]
+ Pages ranked by `sortBy` for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_pages",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "sortBy": sort_by,
+ "limit": limit,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AnalyzeDailyTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopPagesFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopPagesResponse,
+ parse_obj_as(
+ type_=TopPagesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def top_dimensions(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ dimension: TopDimensionsDimension,
+ metric_scope: TopDimensionsMetricScope,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsTopDimensionsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopDimensionsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[TopDimensionsResponse]:
+ """
+ Returns the top values within a chosen `dimension` — top countries, top traffic sources, top campaigns, top audiences, and so on — over a time window, ranked by sessions or users.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ dimension : TopDimensionsDimension
+ The dimension whose top values are ranked. See `TopDimensionsDimension` for the supported values.
+
+ metric_scope : TopDimensionsMetricScope
+ The unit each row's `count` is measured in — sessions or users.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsTopDimensionsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopDimensionsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopDimensionsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[TopDimensionsResponse]
+ Dimension values ranked by `count` for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_dimensions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "dimension": dimension,
+ "metricScope": metric_scope,
+ "limit": limit,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopDimensionsFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopDimensionsResponse,
+ parse_obj_as(
+ type_=TopDimensionsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def top_events(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ limit: typing.Optional[int] = None,
+ timeseries: typing.Optional[AnalyzeDailyTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTopEventsRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TopEventsFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[TopEventsResponse]:
+ """
+ Returns the top events over a time window, ranked by how often they occurred.
+
+ Events are counted individually, not rolled up into sessions, users, or page views — so this report has no `metricScope`. Each row's `count` is how many times the event occurred. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ Set `timeseries[bucketTimeZone]` to attach a daily event count `timeseries` to each row.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `25`, up to a maximum of `250`.
+
+ timeseries : typing.Optional[AnalyzeDailyTimeseriesQuery]
+ Include a daily event count `timeseries` for each row, bucketed in the supplied IANA time zone. Omit this parameter to return ranked rows without per-event timeseries data.
+
+ device_type : typing.Optional[ReportsTopEventsRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TopEventsFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TopEventsFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[TopEventsResponse]
+ Events ranked by how frequently they occurred, for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/top_events",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "limit": limit,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AnalyzeDailyTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TopEventsFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TopEventsResponse,
+ parse_obj_as(
+ type_=TopEventsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def time_on_page(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ metric_scope: TimeOnPageMetricScope,
+ timeseries: typing.Optional[TimeOnPageTimeseriesQuery] = None,
+ device_type: typing.Optional[ReportsTimeOnPageRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[TimeOnPageFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[TimeOnPageResponse]:
+ """
+ Returns the average time on page over a time window — as a single aggregate value, or bucketed by day or week when `timeseries` is supplied.
+
+ Choose how the average is computed with `metricScope` (per session, user, or page view). Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ metric_scope : TimeOnPageMetricScope
+ How the average time on page is computed — per session, user, or page view.
+
+ timeseries : typing.Optional[TimeOnPageTimeseriesQuery]
+ Include bucketed average time data using the supplied granularity and IANA time zone. Omit this parameter to return a single aggregate value for the requested window.
+
+ device_type : typing.Optional[ReportsTimeOnPageRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[TimeOnPageFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `TimeOnPageFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[TimeOnPageResponse]
+ Average time on page for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/time_on_page",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "metricScope": metric_scope,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=TimeOnPageTimeseriesQuery, direction="write"
+ ),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=TimeOnPageFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ TimeOnPageResponse,
+ parse_obj_as(
+ type_=TimeOnPageResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def scroll_depth(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsScrollDepthRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[ScrollDepthFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ScrollDepthResponse]:
+ """
+ Returns the scroll-depth distribution for the site over a time window. Each row reports the scroll depth (in pixels) that a share of page views reached, at fixed breakpoints of 75%, 50%, and 25% of page views.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsScrollDepthRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[ScrollDepthFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `ScrollDepthFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ScrollDepthResponse]
+ Scroll-depth distribution for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/scroll_depth",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=ScrollDepthFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ScrollDepthResponse,
+ parse_obj_as(
+ type_=ScrollDepthResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def browser_window_height(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ device_type: typing.Optional[ReportsBrowserWindowHeightRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[BrowserWindowHeightFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[BrowserWindowHeightResponse]:
+ """
+ Returns the average browser window height (in CSS pixels) for the site over a time window. The response wraps the single aggregate in a one-element `data` array for SDK uniformity with other Analyze reports.
+
+ Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching. Omit `deviceType` to include all devices.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ device_type : typing.Optional[ReportsBrowserWindowHeightRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[BrowserWindowHeightFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `BrowserWindowHeightFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[BrowserWindowHeightResponse]
+ Average browser window height for the requested window and filters.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/browser_window_height",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=BrowserWindowHeightFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ BrowserWindowHeightResponse,
+ parse_obj_as(
+ type_=BrowserWindowHeightResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def page_flow(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ direction: PageFlowDirection,
+ metric_scope: PageFlowMetricScope,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ limit: typing.Optional[int] = None,
+ device_type: typing.Optional[ReportsPageFlowRequestDeviceType] = None,
+ country: typing.Optional[str] = None,
+ page_path: typing.Optional[str] = None,
+ traffic_source: typing.Optional[str] = None,
+ referrer: typing.Optional[str] = None,
+ browser: typing.Optional[str] = None,
+ utm_campaign: typing.Optional[str] = None,
+ utm_medium: typing.Optional[str] = None,
+ utm_source: typing.Optional[str] = None,
+ filter: typing.Optional[PageFlowFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[PageFlowResponse]:
+ """
+ Returns the pages visitors viewed immediately before or after a required anchor page over a time window, ranked by sessions, users, or page views.
+
+ For a CMS template instance, provide both `collectionId` and `itemSlug` with `pageId`. Filter the report with top-level query parameters (`country`, `deviceType`, `pagePath`, etc.) or via the `filter` parameter for multi-value and negation matching.
+
+ This endpoint requires a workspace with the Analyze add-on.
+
+ Each access token can have one Analyze request in flight at a time, across all Analyze endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-01T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be on or after `2025-04-09T00:00:00Z`.
+
+ end_time : dt.datetime
+ Exclusive end of the reporting window. Must be a UTC timestamp in ISO 8601 / RFC 3339 format ending in `Z` (for example, `2026-04-08T00:00:00Z`); numeric offsets such as `-04:00` or `+00:00` are not accepted. Must be greater than `startTime` and within 100 days of it.
+
+ direction : PageFlowDirection
+ Whether to return pages viewed immediately before or after the anchor page.
+
+ metric_scope : PageFlowMetricScope
+ The unit each row's `count` is measured in.
+
+ page_id : str
+ Identifier of the anchor Webflow page.
+
+ collection_id : typing.Optional[str]
+ Identifier of the CMS collection for an anchor template-page instance. Must be provided together with `itemSlug`, or both must be omitted.
+
+ item_slug : typing.Optional[str]
+ Slug of the CMS item for an anchor template-page instance. Must be provided together with `collectionId`, or both must be omitted.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `10`, up to a maximum of `100`.
+
+ device_type : typing.Optional[ReportsPageFlowRequestDeviceType]
+ Restrict the report to a single device type.
+
+ country : typing.Optional[str]
+ Restrict the report to a single country. ISO 3166-1 alpha-2 (two letters, normalized to uppercase).
+
+ page_path : typing.Optional[str]
+ Restrict the report to a single page path.
+
+ traffic_source : typing.Optional[str]
+ Restrict the report to a single traffic source code (for example, `SO` for Organic Search).
+
+ referrer : typing.Optional[str]
+ Restrict the report to a single referrer domain.
+
+ browser : typing.Optional[str]
+ Restrict the report to a single browser.
+
+ utm_campaign : typing.Optional[str]
+ Restrict the report to a single `utm_campaign` value.
+
+ utm_medium : typing.Optional[str]
+ Restrict the report to a single `utm_medium` value.
+
+ utm_source : typing.Optional[str]
+ Restrict the report to a single `utm_source` value.
+
+ filter : typing.Optional[PageFlowFilter]
+ Filter the report by dimension. Use bracket notation. Scalars take a single value (`filter[country][eq]=US`, `filter[country][ne]=US`). Arrays use indexed brackets (`filter[country][in][0]=US&filter[country][in][1]=CA`, `filter[country][nin][0]=US&filter[country][nin][1]=CA`).
+ Each dimension entry takes at least one of `eq`, `in`, `ne`, or `nin`. Filter a given dimension in one place — either a top-level query parameter or a `filter` entry. See the `PageFlowFilter` schema for the full list of supported dimensions.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[PageFlowResponse]
+ Pages viewed immediately before or after the anchor page, ranked by `count`.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/analyze/reports/page_flow",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "direction": direction,
+ "metricScope": metric_scope,
+ "pageId": page_id,
+ "collectionId": collection_id,
+ "itemSlug": item_slug,
+ "limit": limit,
+ "deviceType": device_type,
+ "country": country,
+ "pagePath": page_path,
+ "trafficSource": traffic_source,
+ "referrer": referrer,
+ "browser": browser,
+ "utmCampaign": utm_campaign,
+ "utmMedium": utm_medium,
+ "utmSource": utm_source,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=PageFlowFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ PageFlowResponse,
+ parse_obj_as(
+ type_=PageFlowResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/analyze/resources/reports/types/__init__.py b/src/webflow/resources/analyze/resources/reports/types/__init__.py
new file mode 100644
index 00000000..f44dad1f
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/__init__.py
@@ -0,0 +1,59 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .reports_browser_window_height_request_device_type import ReportsBrowserWindowHeightRequestDeviceType
+ from .reports_page_flow_request_device_type import ReportsPageFlowRequestDeviceType
+ from .reports_scroll_depth_request_device_type import ReportsScrollDepthRequestDeviceType
+ from .reports_time_on_page_request_device_type import ReportsTimeOnPageRequestDeviceType
+ from .reports_top_dimensions_request_device_type import ReportsTopDimensionsRequestDeviceType
+ from .reports_top_events_request_device_type import ReportsTopEventsRequestDeviceType
+ from .reports_top_pages_request_device_type import ReportsTopPagesRequestDeviceType
+ from .reports_traffic_request_device_type import ReportsTrafficRequestDeviceType
+_dynamic_imports: typing.Dict[str, str] = {
+ "ReportsBrowserWindowHeightRequestDeviceType": ".reports_browser_window_height_request_device_type",
+ "ReportsPageFlowRequestDeviceType": ".reports_page_flow_request_device_type",
+ "ReportsScrollDepthRequestDeviceType": ".reports_scroll_depth_request_device_type",
+ "ReportsTimeOnPageRequestDeviceType": ".reports_time_on_page_request_device_type",
+ "ReportsTopDimensionsRequestDeviceType": ".reports_top_dimensions_request_device_type",
+ "ReportsTopEventsRequestDeviceType": ".reports_top_events_request_device_type",
+ "ReportsTopPagesRequestDeviceType": ".reports_top_pages_request_device_type",
+ "ReportsTrafficRequestDeviceType": ".reports_traffic_request_device_type",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "ReportsBrowserWindowHeightRequestDeviceType",
+ "ReportsPageFlowRequestDeviceType",
+ "ReportsScrollDepthRequestDeviceType",
+ "ReportsTimeOnPageRequestDeviceType",
+ "ReportsTopDimensionsRequestDeviceType",
+ "ReportsTopEventsRequestDeviceType",
+ "ReportsTopPagesRequestDeviceType",
+ "ReportsTrafficRequestDeviceType",
+]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_browser_window_height_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_browser_window_height_request_device_type.py
new file mode 100644
index 00000000..ebbfc71a
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_browser_window_height_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsBrowserWindowHeightRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_page_flow_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_page_flow_request_device_type.py
new file mode 100644
index 00000000..f247e2ba
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_page_flow_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsPageFlowRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_scroll_depth_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_scroll_depth_request_device_type.py
new file mode 100644
index 00000000..68516372
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_scroll_depth_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsScrollDepthRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_time_on_page_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_time_on_page_request_device_type.py
new file mode 100644
index 00000000..26062d31
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_time_on_page_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsTimeOnPageRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_top_dimensions_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_top_dimensions_request_device_type.py
new file mode 100644
index 00000000..be530225
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_top_dimensions_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsTopDimensionsRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_top_events_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_top_events_request_device_type.py
new file mode 100644
index 00000000..05592a41
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_top_events_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsTopEventsRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_top_pages_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_top_pages_request_device_type.py
new file mode 100644
index 00000000..e9a70a5f
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_top_pages_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsTopPagesRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/analyze/resources/reports/types/reports_traffic_request_device_type.py b/src/webflow/resources/analyze/resources/reports/types/reports_traffic_request_device_type.py
new file mode 100644
index 00000000..0b1fd972
--- /dev/null
+++ b/src/webflow/resources/analyze/resources/reports/types/reports_traffic_request_device_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsTrafficRequestDeviceType = typing.Union[typing.Literal["desktop", "mobile", "tablet"], typing.Any]
diff --git a/src/webflow/resources/assets/client.py b/src/webflow/resources/assets/client.py
index ae22ab4a..9f533e40 100644
--- a/src/webflow/resources/assets/client.py
+++ b/src/webflow/resources/assets/client.py
@@ -34,8 +34,10 @@ def list(
self,
site_id: str,
*,
+ locale_id: typing.Optional[str] = None,
offset: typing.Optional[int] = None,
limit: typing.Optional[int] = None,
+ folder_id: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Assets:
"""
@@ -48,12 +50,21 @@ def list(
site_id : str
Unique identifier for a Site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
+ folder_id : typing.Optional[str]
+ Filter assets to those in the specified folder and all descendant folders.
+ Must be a 24-character hex ObjectId.
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -71,11 +82,20 @@ def list(
)
client.assets.list(
site_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
offset=1,
limit=1,
+ folder_id="folderId",
)
"""
- _response = self._raw_client.list(site_id, offset=offset, limit=limit, request_options=request_options)
+ _response = self._raw_client.list(
+ site_id,
+ locale_id=locale_id,
+ offset=offset,
+ limit=limit,
+ folder_id=folder_id,
+ request_options=request_options,
+ )
return _response.data
def create(
@@ -145,7 +165,13 @@ def create(
)
return _response.data
- def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Asset:
+ def get(
+ self,
+ asset_id: str,
+ *,
+ locale_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> Asset:
"""
Get details about an asset
@@ -156,6 +182,11 @@ def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions]
asset_id : str
Unique identifier for an Asset on a site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -173,9 +204,10 @@ def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions]
)
client.assets.get(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
"""
- _response = self._raw_client.get(asset_id, request_options=request_options)
+ _response = self._raw_client.get(asset_id, locale_id=locale_id, request_options=request_options)
return _response.data
def delete(self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
@@ -214,8 +246,9 @@ def update(
self,
asset_id: str,
*,
- locale_id: typing.Optional[str] = OMIT,
+ locale_id: typing.Optional[str] = None,
display_name: typing.Optional[str] = OMIT,
+ alt_text: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> Asset:
"""
@@ -229,10 +262,15 @@ def update(
Unique identifier for an Asset on a site
locale_id : typing.Optional[str]
- Unique identifier for a specific locale. Applicable, when using localization.
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
display_name : typing.Optional[str]
- A human readable name for the asset
+ A human readable name for the asset. This value is not localizable.
+
+ alt_text : typing.Optional[str]
+ Alternate text describing the image
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -251,10 +289,11 @@ def update(
)
client.assets.update(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.update(
- asset_id, locale_id=locale_id, display_name=display_name, request_options=request_options
+ asset_id, locale_id=locale_id, display_name=display_name, alt_text=alt_text, request_options=request_options
)
return _response.data
@@ -395,8 +434,10 @@ async def list(
self,
site_id: str,
*,
+ locale_id: typing.Optional[str] = None,
offset: typing.Optional[int] = None,
limit: typing.Optional[int] = None,
+ folder_id: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Assets:
"""
@@ -409,12 +450,21 @@ async def list(
site_id : str
Unique identifier for a Site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
+ folder_id : typing.Optional[str]
+ Filter assets to those in the specified folder and all descendant folders.
+ Must be a 24-character hex ObjectId.
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -437,14 +487,23 @@ async def list(
async def main() -> None:
await client.assets.list(
site_id="580e63e98c9a982ac9b8b741",
+ locale_id="65427cf400e02b306eaa04a0",
offset=1,
limit=1,
+ folder_id="folderId",
)
asyncio.run(main())
"""
- _response = await self._raw_client.list(site_id, offset=offset, limit=limit, request_options=request_options)
+ _response = await self._raw_client.list(
+ site_id,
+ locale_id=locale_id,
+ offset=offset,
+ limit=limit,
+ folder_id=folder_id,
+ request_options=request_options,
+ )
return _response.data
async def create(
@@ -522,7 +581,13 @@ async def main() -> None:
)
return _response.data
- async def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Asset:
+ async def get(
+ self,
+ asset_id: str,
+ *,
+ locale_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> Asset:
"""
Get details about an asset
@@ -533,6 +598,11 @@ async def get(self, asset_id: str, *, request_options: typing.Optional[RequestOp
asset_id : str
Unique identifier for an Asset on a site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -555,12 +625,13 @@ async def get(self, asset_id: str, *, request_options: typing.Optional[RequestOp
async def main() -> None:
await client.assets.get(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
- _response = await self._raw_client.get(asset_id, request_options=request_options)
+ _response = await self._raw_client.get(asset_id, locale_id=locale_id, request_options=request_options)
return _response.data
async def delete(self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
@@ -607,8 +678,9 @@ async def update(
self,
asset_id: str,
*,
- locale_id: typing.Optional[str] = OMIT,
+ locale_id: typing.Optional[str] = None,
display_name: typing.Optional[str] = OMIT,
+ alt_text: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> Asset:
"""
@@ -622,10 +694,15 @@ async def update(
Unique identifier for an Asset on a site
locale_id : typing.Optional[str]
- Unique identifier for a specific locale. Applicable, when using localization.
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
display_name : typing.Optional[str]
- A human readable name for the asset
+ A human readable name for the asset. This value is not localizable.
+
+ alt_text : typing.Optional[str]
+ Alternate text describing the image
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -649,13 +726,14 @@ async def update(
async def main() -> None:
await client.assets.update(
asset_id="580e63fc8c9a982ac9b8b745",
+ locale_id="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
_response = await self._raw_client.update(
- asset_id, locale_id=locale_id, display_name=display_name, request_options=request_options
+ asset_id, locale_id=locale_id, display_name=display_name, alt_text=alt_text, request_options=request_options
)
return _response.data
diff --git a/src/webflow/resources/assets/raw_client.py b/src/webflow/resources/assets/raw_client.py
index 5c99d982..bb22d652 100644
--- a/src/webflow/resources/assets/raw_client.py
+++ b/src/webflow/resources/assets/raw_client.py
@@ -35,8 +35,10 @@ def list(
self,
site_id: str,
*,
+ locale_id: typing.Optional[str] = None,
offset: typing.Optional[int] = None,
limit: typing.Optional[int] = None,
+ folder_id: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[Assets]:
"""
@@ -49,12 +51,21 @@ def list(
site_id : str
Unique identifier for a Site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
+ folder_id : typing.Optional[str]
+ Filter assets to those in the specified folder and all descendant folders.
+ Must be a 24-character hex ObjectId.
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -68,8 +79,10 @@ def list(
base_url=self._client_wrapper.get_environment().base,
method="GET",
params={
+ "localeId": locale_id,
"offset": offset,
"limit": limit,
+ "folderId": folder_id,
},
request_options=request_options,
)
@@ -281,7 +294,13 @@ def create(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
- def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Asset]:
+ def get(
+ self,
+ asset_id: str,
+ *,
+ locale_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[Asset]:
"""
Get details about an asset
@@ -292,6 +311,11 @@ def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions]
asset_id : str
Unique identifier for an Asset on a site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -304,6 +328,9 @@ def get(self, asset_id: str, *, request_options: typing.Optional[RequestOptions]
f"assets/{jsonable_encoder(asset_id)}",
base_url=self._client_wrapper.get_environment().base,
method="GET",
+ params={
+ "localeId": locale_id,
+ },
request_options=request_options,
)
try:
@@ -475,8 +502,9 @@ def update(
self,
asset_id: str,
*,
- locale_id: typing.Optional[str] = OMIT,
+ locale_id: typing.Optional[str] = None,
display_name: typing.Optional[str] = OMIT,
+ alt_text: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[Asset]:
"""
@@ -490,10 +518,15 @@ def update(
Unique identifier for an Asset on a site
locale_id : typing.Optional[str]
- Unique identifier for a specific locale. Applicable, when using localization.
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
display_name : typing.Optional[str]
- A human readable name for the asset
+ A human readable name for the asset. This value is not localizable.
+
+ alt_text : typing.Optional[str]
+ Alternate text describing the image
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -507,9 +540,12 @@ def update(
f"assets/{jsonable_encoder(asset_id)}",
base_url=self._client_wrapper.get_environment().base,
method="PATCH",
- json={
+ params={
"localeId": locale_id,
+ },
+ json={
"displayName": display_name,
+ "altText": alt_text,
},
headers={
"content-type": "application/json",
@@ -922,8 +958,10 @@ async def list(
self,
site_id: str,
*,
+ locale_id: typing.Optional[str] = None,
offset: typing.Optional[int] = None,
limit: typing.Optional[int] = None,
+ folder_id: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[Assets]:
"""
@@ -936,12 +974,21 @@ async def list(
site_id : str
Unique identifier for a Site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
+ folder_id : typing.Optional[str]
+ Filter assets to those in the specified folder and all descendant folders.
+ Must be a 24-character hex ObjectId.
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -955,8 +1002,10 @@ async def list(
base_url=self._client_wrapper.get_environment().base,
method="GET",
params={
+ "localeId": locale_id,
"offset": offset,
"limit": limit,
+ "folderId": folder_id,
},
request_options=request_options,
)
@@ -1169,7 +1218,11 @@ async def create(
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
async def get(
- self, asset_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ self,
+ asset_id: str,
+ *,
+ locale_id: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[Asset]:
"""
Get details about an asset
@@ -1181,6 +1234,11 @@ async def get(
asset_id : str
Unique identifier for an Asset on a site
+ locale_id : typing.Optional[str]
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1193,6 +1251,9 @@ async def get(
f"assets/{jsonable_encoder(asset_id)}",
base_url=self._client_wrapper.get_environment().base,
method="GET",
+ params={
+ "localeId": locale_id,
+ },
request_options=request_options,
)
try:
@@ -1366,8 +1427,9 @@ async def update(
self,
asset_id: str,
*,
- locale_id: typing.Optional[str] = OMIT,
+ locale_id: typing.Optional[str] = None,
display_name: typing.Optional[str] = OMIT,
+ alt_text: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[Asset]:
"""
@@ -1381,10 +1443,15 @@ async def update(
Unique identifier for an Asset on a site
locale_id : typing.Optional[str]
- Unique identifier for a specific locale. Applicable, when using localization.
+ Unique identifier for a specific Locale.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
display_name : typing.Optional[str]
- A human readable name for the asset
+ A human readable name for the asset. This value is not localizable.
+
+ alt_text : typing.Optional[str]
+ Alternate text describing the image
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1398,9 +1465,12 @@ async def update(
f"assets/{jsonable_encoder(asset_id)}",
base_url=self._client_wrapper.get_environment().base,
method="PATCH",
- json={
+ params={
"localeId": locale_id,
+ },
+ json={
"displayName": display_name,
+ "altText": alt_text,
},
headers={
"content-type": "application/json",
diff --git a/src/webflow/resources/collections/__init__.py b/src/webflow/resources/collections/__init__.py
index 07eb5d1e..6dc2a5c1 100644
--- a/src/webflow/resources/collections/__init__.py
+++ b/src/webflow/resources/collections/__init__.py
@@ -15,10 +15,21 @@
ItemsCreateItemRequestBody,
ItemsDeleteItemsLiveRequestItemsItem,
ItemsDeleteItemsRequestItemsItem,
+ ItemsInsertItemsResponse,
+ ItemsListItemsLiveRequestFilterValue,
+ ItemsListItemsLiveRequestFilterValueExists,
+ ItemsListItemsLiveRequestFilterValueIn,
+ ItemsListItemsLiveRequestFilterValueNin,
ItemsListItemsLiveRequestSortBy,
ItemsListItemsLiveRequestSortOrder,
+ ItemsListItemsLiveRequestSortValue,
+ ItemsListItemsRequestFilterValue,
+ ItemsListItemsRequestFilterValueExists,
+ ItemsListItemsRequestFilterValueIn,
+ ItemsListItemsRequestFilterValueNin,
ItemsListItemsRequestSortBy,
ItemsListItemsRequestSortOrder,
+ ItemsListItemsRequestSortValue,
ItemsPublishItemRequest,
ItemsPublishItemRequestItemsItemsItem,
ItemsPublishItemResponse,
@@ -38,10 +49,21 @@
"ItemsCreateItemRequestBody": ".resources",
"ItemsDeleteItemsLiveRequestItemsItem": ".resources",
"ItemsDeleteItemsRequestItemsItem": ".resources",
+ "ItemsInsertItemsResponse": ".resources",
+ "ItemsListItemsLiveRequestFilterValue": ".resources",
+ "ItemsListItemsLiveRequestFilterValueExists": ".resources",
+ "ItemsListItemsLiveRequestFilterValueIn": ".resources",
+ "ItemsListItemsLiveRequestFilterValueNin": ".resources",
"ItemsListItemsLiveRequestSortBy": ".resources",
"ItemsListItemsLiveRequestSortOrder": ".resources",
+ "ItemsListItemsLiveRequestSortValue": ".resources",
+ "ItemsListItemsRequestFilterValue": ".resources",
+ "ItemsListItemsRequestFilterValueExists": ".resources",
+ "ItemsListItemsRequestFilterValueIn": ".resources",
+ "ItemsListItemsRequestFilterValueNin": ".resources",
"ItemsListItemsRequestSortBy": ".resources",
"ItemsListItemsRequestSortOrder": ".resources",
+ "ItemsListItemsRequestSortValue": ".resources",
"ItemsPublishItemRequest": ".resources",
"ItemsPublishItemRequestItemsItemsItem": ".resources",
"ItemsPublishItemResponse": ".resources",
@@ -84,10 +106,21 @@ def __dir__():
"ItemsCreateItemRequestBody",
"ItemsDeleteItemsLiveRequestItemsItem",
"ItemsDeleteItemsRequestItemsItem",
+ "ItemsInsertItemsResponse",
+ "ItemsListItemsLiveRequestFilterValue",
+ "ItemsListItemsLiveRequestFilterValueExists",
+ "ItemsListItemsLiveRequestFilterValueIn",
+ "ItemsListItemsLiveRequestFilterValueNin",
"ItemsListItemsLiveRequestSortBy",
"ItemsListItemsLiveRequestSortOrder",
+ "ItemsListItemsLiveRequestSortValue",
+ "ItemsListItemsRequestFilterValue",
+ "ItemsListItemsRequestFilterValueExists",
+ "ItemsListItemsRequestFilterValueIn",
+ "ItemsListItemsRequestFilterValueNin",
"ItemsListItemsRequestSortBy",
"ItemsListItemsRequestSortOrder",
+ "ItemsListItemsRequestSortValue",
"ItemsPublishItemRequest",
"ItemsPublishItemRequestItemsItemsItem",
"ItemsPublishItemResponse",
diff --git a/src/webflow/resources/collections/client.py b/src/webflow/resources/collections/client.py
index bd4adca2..288f16d7 100644
--- a/src/webflow/resources/collections/client.py
+++ b/src/webflow/resources/collections/client.py
@@ -9,6 +9,7 @@
from ...types.collection import Collection
from ...types.collection_list import CollectionList
from ...types.field_create import FieldCreate
+from ...types.field_group import FieldGroup
from .raw_client import AsyncRawCollectionsClient, RawCollectionsClient
if typing.TYPE_CHECKING:
@@ -223,6 +224,73 @@ def delete(self, collection_id: str, *, request_options: typing.Optional[Request
_response = self._raw_client.delete(collection_id, request_options=request_options)
return _response.data
+ def patch(
+ self,
+ collection_id: str,
+ *,
+ display_name: typing.Optional[str] = OMIT,
+ singular_name: typing.Optional[str] = OMIT,
+ slug: typing.Optional[str] = OMIT,
+ field_groups: typing.Optional[typing.Sequence[FieldGroup]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> Collection:
+ """
+ Update a collection's display name, singular name, slug, or field groups.
+
+ **Field group rules:**
+ - A collection can have a maximum of 50 field groups
+ - Each `displayName` must be unique across all field groups in the collection
+ - Each `fieldId` must be unique across all field groups in the collection
+ - Ecommerce collections do not support field groups
+
+ Required scope | `cms:write`
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ display_name : typing.Optional[str]
+ Name given to the Collection
+
+ singular_name : typing.Optional[str]
+ The name of one Item in Collection (e.g. ”Blog Post” if the Collection is called “Blog Posts”)
+
+ slug : typing.Optional[str]
+ Slug of Collection in Site URL structure
+
+ field_groups : typing.Optional[typing.Sequence[FieldGroup]]
+ The list of field groups in the Collection. Replaces the existing field groups.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Collection
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.collections.patch(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ )
+ """
+ _response = self._raw_client.patch(
+ collection_id,
+ display_name=display_name,
+ singular_name=singular_name,
+ slug=slug,
+ field_groups=field_groups,
+ request_options=request_options,
+ )
+ return _response.data
+
@property
def fields(self):
if self._fields is None:
@@ -482,6 +550,81 @@ async def main() -> None:
_response = await self._raw_client.delete(collection_id, request_options=request_options)
return _response.data
+ async def patch(
+ self,
+ collection_id: str,
+ *,
+ display_name: typing.Optional[str] = OMIT,
+ singular_name: typing.Optional[str] = OMIT,
+ slug: typing.Optional[str] = OMIT,
+ field_groups: typing.Optional[typing.Sequence[FieldGroup]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> Collection:
+ """
+ Update a collection's display name, singular name, slug, or field groups.
+
+ **Field group rules:**
+ - A collection can have a maximum of 50 field groups
+ - Each `displayName` must be unique across all field groups in the collection
+ - Each `fieldId` must be unique across all field groups in the collection
+ - Ecommerce collections do not support field groups
+
+ Required scope | `cms:write`
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ display_name : typing.Optional[str]
+ Name given to the Collection
+
+ singular_name : typing.Optional[str]
+ The name of one Item in Collection (e.g. ”Blog Post” if the Collection is called “Blog Posts”)
+
+ slug : typing.Optional[str]
+ Slug of Collection in Site URL structure
+
+ field_groups : typing.Optional[typing.Sequence[FieldGroup]]
+ The list of field groups in the Collection. Replaces the existing field groups.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Collection
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.collections.patch(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.patch(
+ collection_id,
+ display_name=display_name,
+ singular_name=singular_name,
+ slug=slug,
+ field_groups=field_groups,
+ request_options=request_options,
+ )
+ return _response.data
+
@property
def fields(self):
if self._fields is None:
diff --git a/src/webflow/resources/collections/raw_client.py b/src/webflow/resources/collections/raw_client.py
index ffc84511..019ecdeb 100644
--- a/src/webflow/resources/collections/raw_client.py
+++ b/src/webflow/resources/collections/raw_client.py
@@ -21,6 +21,7 @@
from ...types.collection_list import CollectionList
from ...types.error import Error
from ...types.field_create import FieldCreate
+from ...types.field_group import FieldGroup
from pydantic import ValidationError
# this is used as the default value for optional parameters
@@ -471,6 +472,144 @@ def delete(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ def patch(
+ self,
+ collection_id: str,
+ *,
+ display_name: typing.Optional[str] = OMIT,
+ singular_name: typing.Optional[str] = OMIT,
+ slug: typing.Optional[str] = OMIT,
+ field_groups: typing.Optional[typing.Sequence[FieldGroup]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[Collection]:
+ """
+ Update a collection's display name, singular name, slug, or field groups.
+
+ **Field group rules:**
+ - A collection can have a maximum of 50 field groups
+ - Each `displayName` must be unique across all field groups in the collection
+ - Each `fieldId` must be unique across all field groups in the collection
+ - Ecommerce collections do not support field groups
+
+ Required scope | `cms:write`
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ display_name : typing.Optional[str]
+ Name given to the Collection
+
+ singular_name : typing.Optional[str]
+ The name of one Item in Collection (e.g. ”Blog Post” if the Collection is called “Blog Posts”)
+
+ slug : typing.Optional[str]
+ Slug of Collection in Site URL structure
+
+ field_groups : typing.Optional[typing.Sequence[FieldGroup]]
+ The list of field groups in the Collection. Replaces the existing field groups.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[Collection]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"collections/{jsonable_encoder(collection_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "displayName": display_name,
+ "singularName": singular_name,
+ "slug": slug,
+ "fieldGroups": convert_and_respect_annotation_metadata(
+ object_=field_groups, annotation=typing.Sequence[FieldGroup], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Collection,
+ parse_obj_as(
+ type_=Collection, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
class AsyncRawCollectionsClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -915,3 +1054,141 @@ async def delete(
status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def patch(
+ self,
+ collection_id: str,
+ *,
+ display_name: typing.Optional[str] = OMIT,
+ singular_name: typing.Optional[str] = OMIT,
+ slug: typing.Optional[str] = OMIT,
+ field_groups: typing.Optional[typing.Sequence[FieldGroup]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[Collection]:
+ """
+ Update a collection's display name, singular name, slug, or field groups.
+
+ **Field group rules:**
+ - A collection can have a maximum of 50 field groups
+ - Each `displayName` must be unique across all field groups in the collection
+ - Each `fieldId` must be unique across all field groups in the collection
+ - Ecommerce collections do not support field groups
+
+ Required scope | `cms:write`
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ display_name : typing.Optional[str]
+ Name given to the Collection
+
+ singular_name : typing.Optional[str]
+ The name of one Item in Collection (e.g. ”Blog Post” if the Collection is called “Blog Posts”)
+
+ slug : typing.Optional[str]
+ Slug of Collection in Site URL structure
+
+ field_groups : typing.Optional[typing.Sequence[FieldGroup]]
+ The list of field groups in the Collection. Replaces the existing field groups.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[Collection]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"collections/{jsonable_encoder(collection_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "displayName": display_name,
+ "singularName": singular_name,
+ "slug": slug,
+ "fieldGroups": convert_and_respect_annotation_metadata(
+ object_=field_groups, annotation=typing.Sequence[FieldGroup], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Collection,
+ parse_obj_as(
+ type_=Collection, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/collections/resources/__init__.py b/src/webflow/resources/collections/resources/__init__.py
index 7123bf3f..ff6a0903 100644
--- a/src/webflow/resources/collections/resources/__init__.py
+++ b/src/webflow/resources/collections/resources/__init__.py
@@ -16,10 +16,21 @@
ItemsCreateItemRequestBody,
ItemsDeleteItemsLiveRequestItemsItem,
ItemsDeleteItemsRequestItemsItem,
+ ItemsInsertItemsResponse,
+ ItemsListItemsLiveRequestFilterValue,
+ ItemsListItemsLiveRequestFilterValueExists,
+ ItemsListItemsLiveRequestFilterValueIn,
+ ItemsListItemsLiveRequestFilterValueNin,
ItemsListItemsLiveRequestSortBy,
ItemsListItemsLiveRequestSortOrder,
+ ItemsListItemsLiveRequestSortValue,
+ ItemsListItemsRequestFilterValue,
+ ItemsListItemsRequestFilterValueExists,
+ ItemsListItemsRequestFilterValueIn,
+ ItemsListItemsRequestFilterValueNin,
ItemsListItemsRequestSortBy,
ItemsListItemsRequestSortOrder,
+ ItemsListItemsRequestSortValue,
ItemsPublishItemRequest,
ItemsPublishItemRequestItemsItemsItem,
ItemsPublishItemResponse,
@@ -37,10 +48,21 @@
"ItemsCreateItemRequestBody": ".items",
"ItemsDeleteItemsLiveRequestItemsItem": ".items",
"ItemsDeleteItemsRequestItemsItem": ".items",
+ "ItemsInsertItemsResponse": ".items",
+ "ItemsListItemsLiveRequestFilterValue": ".items",
+ "ItemsListItemsLiveRequestFilterValueExists": ".items",
+ "ItemsListItemsLiveRequestFilterValueIn": ".items",
+ "ItemsListItemsLiveRequestFilterValueNin": ".items",
"ItemsListItemsLiveRequestSortBy": ".items",
"ItemsListItemsLiveRequestSortOrder": ".items",
+ "ItemsListItemsLiveRequestSortValue": ".items",
+ "ItemsListItemsRequestFilterValue": ".items",
+ "ItemsListItemsRequestFilterValueExists": ".items",
+ "ItemsListItemsRequestFilterValueIn": ".items",
+ "ItemsListItemsRequestFilterValueNin": ".items",
"ItemsListItemsRequestSortBy": ".items",
"ItemsListItemsRequestSortOrder": ".items",
+ "ItemsListItemsRequestSortValue": ".items",
"ItemsPublishItemRequest": ".items",
"ItemsPublishItemRequestItemsItemsItem": ".items",
"ItemsPublishItemResponse": ".items",
@@ -83,10 +105,21 @@ def __dir__():
"ItemsCreateItemRequestBody",
"ItemsDeleteItemsLiveRequestItemsItem",
"ItemsDeleteItemsRequestItemsItem",
+ "ItemsInsertItemsResponse",
+ "ItemsListItemsLiveRequestFilterValue",
+ "ItemsListItemsLiveRequestFilterValueExists",
+ "ItemsListItemsLiveRequestFilterValueIn",
+ "ItemsListItemsLiveRequestFilterValueNin",
"ItemsListItemsLiveRequestSortBy",
"ItemsListItemsLiveRequestSortOrder",
+ "ItemsListItemsLiveRequestSortValue",
+ "ItemsListItemsRequestFilterValue",
+ "ItemsListItemsRequestFilterValueExists",
+ "ItemsListItemsRequestFilterValueIn",
+ "ItemsListItemsRequestFilterValueNin",
"ItemsListItemsRequestSortBy",
"ItemsListItemsRequestSortOrder",
+ "ItemsListItemsRequestSortValue",
"ItemsPublishItemRequest",
"ItemsPublishItemRequestItemsItemsItem",
"ItemsPublishItemResponse",
diff --git a/src/webflow/resources/collections/resources/fields/raw_client.py b/src/webflow/resources/collections/resources/fields/raw_client.py
index 31f26bef..cc819d1d 100644
--- a/src/webflow/resources/collections/resources/fields/raw_client.py
+++ b/src/webflow/resources/collections/resources/fields/raw_client.py
@@ -62,9 +62,6 @@ def create(
base_url=self._client_wrapper.get_environment().base,
method="POST",
json=convert_and_respect_annotation_metadata(object_=request, annotation=FieldCreate, direction="write"),
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -415,9 +412,6 @@ async def create(
base_url=self._client_wrapper.get_environment().base,
method="POST",
json=convert_and_respect_annotation_metadata(object_=request, annotation=FieldCreate, direction="write"),
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/collections/resources/items/__init__.py b/src/webflow/resources/collections/resources/items/__init__.py
index ae894396..e9b2e71a 100644
--- a/src/webflow/resources/collections/resources/items/__init__.py
+++ b/src/webflow/resources/collections/resources/items/__init__.py
@@ -15,10 +15,21 @@
ItemsCreateItemRequestBody,
ItemsDeleteItemsLiveRequestItemsItem,
ItemsDeleteItemsRequestItemsItem,
+ ItemsInsertItemsResponse,
+ ItemsListItemsLiveRequestFilterValue,
+ ItemsListItemsLiveRequestFilterValueExists,
+ ItemsListItemsLiveRequestFilterValueIn,
+ ItemsListItemsLiveRequestFilterValueNin,
ItemsListItemsLiveRequestSortBy,
ItemsListItemsLiveRequestSortOrder,
+ ItemsListItemsLiveRequestSortValue,
+ ItemsListItemsRequestFilterValue,
+ ItemsListItemsRequestFilterValueExists,
+ ItemsListItemsRequestFilterValueIn,
+ ItemsListItemsRequestFilterValueNin,
ItemsListItemsRequestSortBy,
ItemsListItemsRequestSortOrder,
+ ItemsListItemsRequestSortValue,
ItemsPublishItemRequest,
ItemsPublishItemRequestItemsItemsItem,
ItemsPublishItemResponse,
@@ -36,10 +47,21 @@
"ItemsCreateItemRequestBody": ".types",
"ItemsDeleteItemsLiveRequestItemsItem": ".types",
"ItemsDeleteItemsRequestItemsItem": ".types",
+ "ItemsInsertItemsResponse": ".types",
+ "ItemsListItemsLiveRequestFilterValue": ".types",
+ "ItemsListItemsLiveRequestFilterValueExists": ".types",
+ "ItemsListItemsLiveRequestFilterValueIn": ".types",
+ "ItemsListItemsLiveRequestFilterValueNin": ".types",
"ItemsListItemsLiveRequestSortBy": ".types",
"ItemsListItemsLiveRequestSortOrder": ".types",
+ "ItemsListItemsLiveRequestSortValue": ".types",
+ "ItemsListItemsRequestFilterValue": ".types",
+ "ItemsListItemsRequestFilterValueExists": ".types",
+ "ItemsListItemsRequestFilterValueIn": ".types",
+ "ItemsListItemsRequestFilterValueNin": ".types",
"ItemsListItemsRequestSortBy": ".types",
"ItemsListItemsRequestSortOrder": ".types",
+ "ItemsListItemsRequestSortValue": ".types",
"ItemsPublishItemRequest": ".types",
"ItemsPublishItemRequestItemsItemsItem": ".types",
"ItemsPublishItemResponse": ".types",
@@ -80,10 +102,21 @@ def __dir__():
"ItemsCreateItemRequestBody",
"ItemsDeleteItemsLiveRequestItemsItem",
"ItemsDeleteItemsRequestItemsItem",
+ "ItemsInsertItemsResponse",
+ "ItemsListItemsLiveRequestFilterValue",
+ "ItemsListItemsLiveRequestFilterValueExists",
+ "ItemsListItemsLiveRequestFilterValueIn",
+ "ItemsListItemsLiveRequestFilterValueNin",
"ItemsListItemsLiveRequestSortBy",
"ItemsListItemsLiveRequestSortOrder",
+ "ItemsListItemsLiveRequestSortValue",
+ "ItemsListItemsRequestFilterValue",
+ "ItemsListItemsRequestFilterValueExists",
+ "ItemsListItemsRequestFilterValueIn",
+ "ItemsListItemsRequestFilterValueNin",
"ItemsListItemsRequestSortBy",
"ItemsListItemsRequestSortOrder",
+ "ItemsListItemsRequestSortValue",
"ItemsPublishItemRequest",
"ItemsPublishItemRequestItemsItemsItem",
"ItemsPublishItemResponse",
diff --git a/src/webflow/resources/collections/resources/items/client.py b/src/webflow/resources/collections/resources/items/client.py
index 16f1d5c9..7ab96b5b 100644
--- a/src/webflow/resources/collections/resources/items/client.py
+++ b/src/webflow/resources/collections/resources/items/client.py
@@ -10,18 +10,28 @@
from .....types.collection_item_list_no_pagination import CollectionItemListNoPagination
from .....types.collection_item_patch_single_field_data import CollectionItemPatchSingleFieldData
from .....types.collection_item_with_id_input import CollectionItemWithIdInput
+from .....types.insert_collection_item import InsertCollectionItem
+from .....types.items_list_items_live_request_created_on import ItemsListItemsLiveRequestCreatedOn
from .....types.items_list_items_live_request_last_published import ItemsListItemsLiveRequestLastPublished
+from .....types.items_list_items_live_request_last_updated import ItemsListItemsLiveRequestLastUpdated
+from .....types.items_list_items_request_created_on import ItemsListItemsRequestCreatedOn
from .....types.items_list_items_request_last_published import ItemsListItemsRequestLastPublished
+from .....types.items_list_items_request_last_updated import ItemsListItemsRequestLastUpdated
from .raw_client import AsyncRawItemsClient, RawItemsClient
from .types.create_bulk_collection_item_request_body_field_data import CreateBulkCollectionItemRequestBodyFieldData
from .types.items_create_item_live_request_body import ItemsCreateItemLiveRequestBody
from .types.items_create_item_request_body import ItemsCreateItemRequestBody
from .types.items_delete_items_live_request_items_item import ItemsDeleteItemsLiveRequestItemsItem
from .types.items_delete_items_request_items_item import ItemsDeleteItemsRequestItemsItem
+from .types.items_insert_items_response import ItemsInsertItemsResponse
+from .types.items_list_items_live_request_filter_value import ItemsListItemsLiveRequestFilterValue
from .types.items_list_items_live_request_sort_by import ItemsListItemsLiveRequestSortBy
from .types.items_list_items_live_request_sort_order import ItemsListItemsLiveRequestSortOrder
+from .types.items_list_items_live_request_sort_value import ItemsListItemsLiveRequestSortValue
+from .types.items_list_items_request_filter_value import ItemsListItemsRequestFilterValue
from .types.items_list_items_request_sort_by import ItemsListItemsRequestSortBy
from .types.items_list_items_request_sort_order import ItemsListItemsRequestSortOrder
+from .types.items_list_items_request_sort_value import ItemsListItemsRequestSortValue
from .types.items_publish_item_request import ItemsPublishItemRequest
from .types.items_publish_item_response import ItemsPublishItemResponse
from .types.items_update_items_response import ItemsUpdateItemsResponse
@@ -54,14 +64,30 @@ def list_items(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemList:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List of all Items within a Collection.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -84,15 +110,84 @@ def list_items(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -115,8 +210,9 @@ def list_items(
limit=1,
name="name",
slug="slug",
- sort_by="lastPublished",
+ sort_by="createdOn",
sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.list_items(
@@ -126,9 +222,14 @@ def list_items(
limit=limit,
name=name,
slug=slug,
+ created_on=created_on,
last_published=last_published,
+ last_updated=last_updated,
+ filter=filter,
sort_by=sort_by,
sort_order=sort_order,
+ sort=sort,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -142,10 +243,47 @@ def create_item(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create Item(s) in a Collection.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which accepts both request shapes below and can create an item in several locales under one ID. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how each shape maps.
+
- To create items across multiple locales, please use [this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create an item in several locales under one ID, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items).
Required scope | `CMS:write`
@@ -255,12 +393,25 @@ def update_items(
request_options: typing.Optional[RequestOptions] = None,
) -> ItemsUpdateItemsResponse:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single item or multiple items in a Collection.
The limit for this endpoint is 100 items.
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -345,18 +496,34 @@ def list_items_live(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsLiveRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsLiveRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsLiveRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsLiveRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsLiveRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemList:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List all published items in a collection.
Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -379,15 +546,84 @@ def list_items_live(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsLiveRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsLiveRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsLiveRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsLiveRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsLiveRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -410,8 +646,9 @@ def list_items_live(
limit=1,
name="name",
slug="slug",
- sort_by="lastPublished",
+ sort_by="createdOn",
sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.list_items_live(
@@ -421,9 +658,14 @@ def list_items_live(
limit=limit,
name=name,
slug=slug,
+ created_on=created_on,
last_published=last_published,
+ last_updated=last_updated,
+ filter=filter,
sort_by=sort_by,
sort_order=sort_order,
+ sort=sort,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -437,11 +679,43 @@ def create_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
- Create item(s) in a collection that will be immediately published to the live site.
-
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
- To create items across multiple locales, [please use this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ Create item(s) in a collection that will be immediately published to the live site.
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create items in several locales, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items) and then [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item). Create Items writes staged content only.
Required scope | `CMS:write`
@@ -547,6 +821,10 @@ def update_items_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemListNoPagination:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single published item or multiple published items (up to 100) in a Collection
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
@@ -638,8 +916,16 @@ def create_items(
request_options: typing.Optional[RequestOptions] = None,
) -> BulkCollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which does everything this endpoint does and can also add locale variants to an existing item. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how the request shape maps.
+
+
- This endpoint can create up to 100 items in a request.
- If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
@@ -709,15 +995,115 @@ def create_items(
)
return _response.data
+ def insert_items(
+ self,
+ collection_id: str,
+ *,
+ items: typing.Sequence[InsertCollectionItem],
+ skip_invalid_files: typing.Optional[bool] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ItemsInsertItemsResponse:
+ """
+ Create one or more staged items in a CMS Collection.
+
+ Send an `items` array with `fieldData` for each item. By default, new items are created in the primary locale. To choose other locales or add variants to an existing item, expand the options below.
+
+ - **Publishing:** Set `isDraft: false` to queue an item for the next site publish. Items are created as drafts (`isDraft: true`) by default. To publish individual items, use [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item).
+ - **Request limit:** Up to 100 items, counting every locale variant across all entries. One item in three locales counts as three; an item in the primary locale only counts as one.
+
+ Required scope | `cms:write`
+
+
+
+ Each new item gets one ID. Its locale variants share that ID and the same `fieldData`.
+
+ - **Selected locales:** Set `cmsLocaleIds` to the locale IDs you want to create the item in.
+ - **All site locales:** Set `allCmsLocales: true` to include every current site locale, including the primary locale, without listing IDs.
+ - **Primary locale only:** Omit both `cmsLocaleIds` and `allCmsLocales`.
+
+ Use only one locale selector per entry. `allCmsLocales` is available only for new items and accepts only `true`; omit it instead of sending `false`.
+
+
+
+ Set `id` to an existing item's ID and provide the new locales in `cmsLocaleIds`. The variants reuse that ID. The item must exist and must not already have a variant in any requested locale.
+
+ Send the source content in `fieldData` as a starting point for translators. To provide different content per locale, send multiple entries with the same `id`, each with one locale in `cmsLocaleIds` and its own `fieldData`.
+
+
+
+ These requests return a `400`:
+
+ - Combining `allCmsLocales` with `cmsLocaleIds` (even an empty array) or `id`.
+ - Setting `allCmsLocales` to `false`, a string, or `null`.
+ - Sending unrecognized properties, including singular `cmsLocaleId`. Use `cmsLocaleIds` (plural).
+ - Repeating a locale within one entry, or the same `id`/locale pair across entries.
+
+ Check the response's `message` and `details` for validation errors. Some errors identify an entry, such as `items[2].cmsLocaleIds`. Invalid locale selector combinations return a message with an empty `details` array.
+
+
+
+ Moving from `POST /items` or `POST /items/bulk`? See [Creating collection items](/data/docs/working-with-the-cms/create-items) for request mappings.
+
+ Rich Text fields can include Webflow components. See [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for markup and constraints.
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ items : typing.Sequence[InsertCollectionItem]
+ The items to create. A request can create up to 100 items, counted as the sum of the locales across every entry. An `allCmsLocales: true` entry counts once per current site locale; a new-item entry with neither locale selector counts as one.
+
+ skip_invalid_files : typing.Optional[bool]
+ When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ItemsInsertItemsResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import InsertCollectionItem, InsertCollectionItemFieldData, Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.collections.items.insert_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ items=[
+ InsertCollectionItem(
+ field_data=InsertCollectionItemFieldData(
+ name="Mostly Harmless",
+ slug="mostly-harmless",
+ ),
+ )
+ ],
+ )
+ """
+ _response = self._raw_client.insert_items(
+ collection_id, items=items, skip_invalid_files=skip_invalid_files, request_options=request_options
+ )
+ return _response.data
+
def get_item(
self,
collection_id: str,
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection Item.
Required scope | `CMS:read`
@@ -731,7 +1117,20 @@ def get_item(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -752,10 +1151,15 @@ def get_item(
collection_id="580e63fc8c9a982ac9b8b745",
item_id="580e64008c9a982ac9b8b754",
cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.get_item(
- collection_id, item_id, cms_locale_id=cms_locale_id, request_options=request_options
+ collection_id,
+ item_id,
+ cms_locale_id=cms_locale_id,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
@@ -825,8 +1229,21 @@ def update_item(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected Item in a Collection.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -859,7 +1276,12 @@ def update_item(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -912,9 +1334,14 @@ def get_item_live(
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection live Item.
@@ -932,7 +1359,20 @@ def get_item_live(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -953,10 +1393,15 @@ def get_item_live(
collection_id="580e63fc8c9a982ac9b8b745",
item_id="580e64008c9a982ac9b8b754",
cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.get_item_live(
- collection_id, item_id, cms_locale_id=cms_locale_id, request_options=request_options
+ collection_id,
+ item_id,
+ cms_locale_id=cms_locale_id,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
@@ -1028,6 +1473,10 @@ def update_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
Required scope | `CMS:write`
@@ -1062,7 +1511,12 @@ def update_item_live(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -1119,6 +1573,9 @@ def publish_item(
"""
Publish an item or multiple items.
+ Publishing a draft item automatically sets `isDraft` to `false`.
+ You don't need to update the item's draft status before calling this endpoint.
+
Required scope | `cms:write`
Parameters
@@ -1183,14 +1640,30 @@ async def list_items(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemList:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List of all Items within a Collection.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -1213,15 +1686,84 @@ async def list_items(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1249,8 +1791,9 @@ async def main() -> None:
limit=1,
name="name",
slug="slug",
- sort_by="lastPublished",
+ sort_by="createdOn",
sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
)
@@ -1263,9 +1806,14 @@ async def main() -> None:
limit=limit,
name=name,
slug=slug,
+ created_on=created_on,
last_published=last_published,
+ last_updated=last_updated,
+ filter=filter,
sort_by=sort_by,
sort_order=sort_order,
+ sort=sort,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -1279,10 +1827,47 @@ async def create_item(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create Item(s) in a Collection.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which accepts both request shapes below and can create an item in several locales under one ID. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how each shape maps.
+
- To create items across multiple locales, please use [this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create an item in several locales under one ID, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items).
Required scope | `CMS:write`
@@ -1408,12 +1993,25 @@ async def update_items(
request_options: typing.Optional[RequestOptions] = None,
) -> ItemsUpdateItemsResponse:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single item or multiple items in a Collection.
The limit for this endpoint is 100 items.
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -1506,18 +2104,34 @@ async def list_items_live(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsLiveRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsLiveRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsLiveRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsLiveRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsLiveRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemList:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List all published items in a collection.
Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -1540,15 +2154,84 @@ async def list_items_live(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsLiveRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsLiveRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsLiveRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsLiveRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsLiveRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1576,8 +2259,9 @@ async def main() -> None:
limit=1,
name="name",
slug="slug",
- sort_by="lastPublished",
+ sort_by="createdOn",
sort_order="asc",
+ translatable="65427cf400e02b306eaa04a0",
)
@@ -1590,9 +2274,14 @@ async def main() -> None:
limit=limit,
name=name,
slug=slug,
+ created_on=created_on,
last_published=last_published,
+ last_updated=last_updated,
+ filter=filter,
sort_by=sort_by,
sort_order=sort_order,
+ sort=sort,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -1606,11 +2295,43 @@ async def create_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
- Create item(s) in a collection that will be immediately published to the live site.
-
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
- To create items across multiple locales, [please use this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ Create item(s) in a collection that will be immediately published to the live site.
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create items in several locales, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items) and then [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item). Create Items writes staged content only.
Required scope | `CMS:write`
@@ -1734,6 +2455,10 @@ async def update_items_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItemListNoPagination:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single published item or multiple published items (up to 100) in a Collection
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
@@ -1833,8 +2558,16 @@ async def create_items(
request_options: typing.Optional[RequestOptions] = None,
) -> BulkCollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which does everything this endpoint does and can also add locale variants to an existing item. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how the request shape maps.
+
+
- This endpoint can create up to 100 items in a request.
- If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
@@ -1912,15 +2645,127 @@ async def main() -> None:
)
return _response.data
+ async def insert_items(
+ self,
+ collection_id: str,
+ *,
+ items: typing.Sequence[InsertCollectionItem],
+ skip_invalid_files: typing.Optional[bool] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> ItemsInsertItemsResponse:
+ """
+ Create one or more staged items in a CMS Collection.
+
+ Send an `items` array with `fieldData` for each item. By default, new items are created in the primary locale. To choose other locales or add variants to an existing item, expand the options below.
+
+ - **Publishing:** Set `isDraft: false` to queue an item for the next site publish. Items are created as drafts (`isDraft: true`) by default. To publish individual items, use [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item).
+ - **Request limit:** Up to 100 items, counting every locale variant across all entries. One item in three locales counts as three; an item in the primary locale only counts as one.
+
+ Required scope | `cms:write`
+
+
+
+ Each new item gets one ID. Its locale variants share that ID and the same `fieldData`.
+
+ - **Selected locales:** Set `cmsLocaleIds` to the locale IDs you want to create the item in.
+ - **All site locales:** Set `allCmsLocales: true` to include every current site locale, including the primary locale, without listing IDs.
+ - **Primary locale only:** Omit both `cmsLocaleIds` and `allCmsLocales`.
+
+ Use only one locale selector per entry. `allCmsLocales` is available only for new items and accepts only `true`; omit it instead of sending `false`.
+
+
+
+ Set `id` to an existing item's ID and provide the new locales in `cmsLocaleIds`. The variants reuse that ID. The item must exist and must not already have a variant in any requested locale.
+
+ Send the source content in `fieldData` as a starting point for translators. To provide different content per locale, send multiple entries with the same `id`, each with one locale in `cmsLocaleIds` and its own `fieldData`.
+
+
+
+ These requests return a `400`:
+
+ - Combining `allCmsLocales` with `cmsLocaleIds` (even an empty array) or `id`.
+ - Setting `allCmsLocales` to `false`, a string, or `null`.
+ - Sending unrecognized properties, including singular `cmsLocaleId`. Use `cmsLocaleIds` (plural).
+ - Repeating a locale within one entry, or the same `id`/locale pair across entries.
+
+ Check the response's `message` and `details` for validation errors. Some errors identify an entry, such as `items[2].cmsLocaleIds`. Invalid locale selector combinations return a message with an empty `details` array.
+
+
+
+ Moving from `POST /items` or `POST /items/bulk`? See [Creating collection items](/data/docs/working-with-the-cms/create-items) for request mappings.
+
+ Rich Text fields can include Webflow components. See [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for markup and constraints.
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ items : typing.Sequence[InsertCollectionItem]
+ The items to create. A request can create up to 100 items, counted as the sum of the locales across every entry. An `allCmsLocales: true` entry counts once per current site locale; a new-item entry with neither locale selector counts as one.
+
+ skip_invalid_files : typing.Optional[bool]
+ When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ ItemsInsertItemsResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import (
+ AsyncWebflow,
+ InsertCollectionItem,
+ InsertCollectionItemFieldData,
+ )
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.collections.items.insert_items(
+ collection_id="580e63fc8c9a982ac9b8b745",
+ skip_invalid_files=True,
+ items=[
+ InsertCollectionItem(
+ field_data=InsertCollectionItemFieldData(
+ name="Mostly Harmless",
+ slug="mostly-harmless",
+ ),
+ )
+ ],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.insert_items(
+ collection_id, items=items, skip_invalid_files=skip_invalid_files, request_options=request_options
+ )
+ return _response.data
+
async def get_item(
self,
collection_id: str,
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection Item.
Required scope | `CMS:read`
@@ -1934,7 +2779,20 @@ async def get_item(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1960,13 +2818,18 @@ async def main() -> None:
collection_id="580e63fc8c9a982ac9b8b745",
item_id="580e64008c9a982ac9b8b754",
cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
_response = await self._raw_client.get_item(
- collection_id, item_id, cms_locale_id=cms_locale_id, request_options=request_options
+ collection_id,
+ item_id,
+ cms_locale_id=cms_locale_id,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
@@ -2044,8 +2907,21 @@ async def update_item(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected Item in a Collection.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -2078,7 +2954,12 @@ async def update_item(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -2139,9 +3020,14 @@ async def get_item_live(
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection live Item.
@@ -2159,7 +3045,20 @@ async def get_item_live(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -2185,13 +3084,18 @@ async def main() -> None:
collection_id="580e63fc8c9a982ac9b8b745",
item_id="580e64008c9a982ac9b8b754",
cms_locale_id="cmsLocaleId",
+ translatable="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
_response = await self._raw_client.get_item_live(
- collection_id, item_id, cms_locale_id=cms_locale_id, request_options=request_options
+ collection_id,
+ item_id,
+ cms_locale_id=cms_locale_id,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
@@ -2271,6 +3175,10 @@ async def update_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> CollectionItem:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
Required scope | `CMS:write`
@@ -2305,7 +3213,12 @@ async def update_item_live(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -2370,6 +3283,9 @@ async def publish_item(
"""
Publish an item or multiple items.
+ Publishing a draft item automatically sets `isDraft` to `false`.
+ You don't need to update the item's draft status before calling this endpoint.
+
Required scope | `cms:write`
Parameters
diff --git a/src/webflow/resources/collections/resources/items/raw_client.py b/src/webflow/resources/collections/resources/items/raw_client.py
index 010bbfd7..e139634d 100644
--- a/src/webflow/resources/collections/resources/items/raw_client.py
+++ b/src/webflow/resources/collections/resources/items/raw_client.py
@@ -24,17 +24,27 @@
from .....types.collection_item_patch_single_field_data import CollectionItemPatchSingleFieldData
from .....types.collection_item_with_id_input import CollectionItemWithIdInput
from .....types.error import Error
+from .....types.insert_collection_item import InsertCollectionItem
+from .....types.items_list_items_live_request_created_on import ItemsListItemsLiveRequestCreatedOn
from .....types.items_list_items_live_request_last_published import ItemsListItemsLiveRequestLastPublished
+from .....types.items_list_items_live_request_last_updated import ItemsListItemsLiveRequestLastUpdated
+from .....types.items_list_items_request_created_on import ItemsListItemsRequestCreatedOn
from .....types.items_list_items_request_last_published import ItemsListItemsRequestLastPublished
+from .....types.items_list_items_request_last_updated import ItemsListItemsRequestLastUpdated
from .types.create_bulk_collection_item_request_body_field_data import CreateBulkCollectionItemRequestBodyFieldData
from .types.items_create_item_live_request_body import ItemsCreateItemLiveRequestBody
from .types.items_create_item_request_body import ItemsCreateItemRequestBody
from .types.items_delete_items_live_request_items_item import ItemsDeleteItemsLiveRequestItemsItem
from .types.items_delete_items_request_items_item import ItemsDeleteItemsRequestItemsItem
+from .types.items_insert_items_response import ItemsInsertItemsResponse
+from .types.items_list_items_live_request_filter_value import ItemsListItemsLiveRequestFilterValue
from .types.items_list_items_live_request_sort_by import ItemsListItemsLiveRequestSortBy
from .types.items_list_items_live_request_sort_order import ItemsListItemsLiveRequestSortOrder
+from .types.items_list_items_live_request_sort_value import ItemsListItemsLiveRequestSortValue
+from .types.items_list_items_request_filter_value import ItemsListItemsRequestFilterValue
from .types.items_list_items_request_sort_by import ItemsListItemsRequestSortBy
from .types.items_list_items_request_sort_order import ItemsListItemsRequestSortOrder
+from .types.items_list_items_request_sort_value import ItemsListItemsRequestSortValue
from .types.items_publish_item_request import ItemsPublishItemRequest
from .types.items_publish_item_response import ItemsPublishItemResponse
from .types.items_update_items_response import ItemsUpdateItemsResponse
@@ -57,14 +67,30 @@ def list_items(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItemList]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List of all Items within a Collection.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -87,15 +113,84 @@ def list_items(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -114,11 +209,24 @@ def list_items(
"limit": limit,
"name": name,
"slug": slug,
+ "createdOn": convert_and_respect_annotation_metadata(
+ object_=created_on, annotation=ItemsListItemsRequestCreatedOn, direction="write"
+ ),
"lastPublished": convert_and_respect_annotation_metadata(
object_=last_published, annotation=ItemsListItemsRequestLastPublished, direction="write"
),
+ "lastUpdated": convert_and_respect_annotation_metadata(
+ object_=last_updated, annotation=ItemsListItemsRequestLastUpdated, direction="write"
+ ),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter,
+ annotation=typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]],
+ direction="write",
+ ),
"sortBy": sort_by,
"sortOrder": sort_order,
+ "sort": sort,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -205,10 +313,47 @@ def create_item(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create Item(s) in a Collection.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which accepts both request shapes below and can create an item in several locales under one ID. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how each shape maps.
+
- To create items across multiple locales, please use [this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create an item in several locales under one ID, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items).
Required scope | `CMS:write`
@@ -450,12 +595,25 @@ def update_items(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[ItemsUpdateItemsResponse]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single item or multiple items in a Collection.
The limit for this endpoint is 100 items.
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -577,18 +735,34 @@ def list_items_live(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsLiveRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsLiveRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsLiveRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsLiveRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsLiveRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItemList]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List all published items in a collection.
Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -611,15 +785,84 @@ def list_items_live(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsLiveRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsLiveRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsLiveRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsLiveRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsLiveRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -638,11 +881,24 @@ def list_items_live(
"limit": limit,
"name": name,
"slug": slug,
+ "createdOn": convert_and_respect_annotation_metadata(
+ object_=created_on, annotation=ItemsListItemsLiveRequestCreatedOn, direction="write"
+ ),
"lastPublished": convert_and_respect_annotation_metadata(
object_=last_published, annotation=ItemsListItemsLiveRequestLastPublished, direction="write"
),
+ "lastUpdated": convert_and_respect_annotation_metadata(
+ object_=last_updated, annotation=ItemsListItemsLiveRequestLastUpdated, direction="write"
+ ),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter,
+ annotation=typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]],
+ direction="write",
+ ),
"sortBy": sort_by,
"sortOrder": sort_order,
+ "sort": sort,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -729,11 +985,43 @@ def create_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
- Create item(s) in a collection that will be immediately published to the live site.
-
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
- To create items across multiple locales, [please use this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ Create item(s) in a collection that will be immediately published to the live site.
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create items in several locales, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items) and then [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item). Create Items writes staged content only.
Required scope | `CMS:write`
@@ -964,6 +1252,10 @@ def update_items_live(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItemListNoPagination]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single published item or multiple published items (up to 100) in a Collection
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
@@ -1103,8 +1395,16 @@ def create_items(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[BulkCollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which does everything this endpoint does and can also add locale variants to an existing item. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how the request shape maps.
+
+
- This endpoint can create up to 100 items in a request.
- If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
@@ -1234,15 +1534,193 @@ def create_items(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ def insert_items(
+ self,
+ collection_id: str,
+ *,
+ items: typing.Sequence[InsertCollectionItem],
+ skip_invalid_files: typing.Optional[bool] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[ItemsInsertItemsResponse]:
+ """
+ Create one or more staged items in a CMS Collection.
+
+ Send an `items` array with `fieldData` for each item. By default, new items are created in the primary locale. To choose other locales or add variants to an existing item, expand the options below.
+
+ - **Publishing:** Set `isDraft: false` to queue an item for the next site publish. Items are created as drafts (`isDraft: true`) by default. To publish individual items, use [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item).
+ - **Request limit:** Up to 100 items, counting every locale variant across all entries. One item in three locales counts as three; an item in the primary locale only counts as one.
+
+ Required scope | `cms:write`
+
+
+
+ Each new item gets one ID. Its locale variants share that ID and the same `fieldData`.
+
+ - **Selected locales:** Set `cmsLocaleIds` to the locale IDs you want to create the item in.
+ - **All site locales:** Set `allCmsLocales: true` to include every current site locale, including the primary locale, without listing IDs.
+ - **Primary locale only:** Omit both `cmsLocaleIds` and `allCmsLocales`.
+
+ Use only one locale selector per entry. `allCmsLocales` is available only for new items and accepts only `true`; omit it instead of sending `false`.
+
+
+
+ Set `id` to an existing item's ID and provide the new locales in `cmsLocaleIds`. The variants reuse that ID. The item must exist and must not already have a variant in any requested locale.
+
+ Send the source content in `fieldData` as a starting point for translators. To provide different content per locale, send multiple entries with the same `id`, each with one locale in `cmsLocaleIds` and its own `fieldData`.
+
+
+
+ These requests return a `400`:
+
+ - Combining `allCmsLocales` with `cmsLocaleIds` (even an empty array) or `id`.
+ - Setting `allCmsLocales` to `false`, a string, or `null`.
+ - Sending unrecognized properties, including singular `cmsLocaleId`. Use `cmsLocaleIds` (plural).
+ - Repeating a locale within one entry, or the same `id`/locale pair across entries.
+
+ Check the response's `message` and `details` for validation errors. Some errors identify an entry, such as `items[2].cmsLocaleIds`. Invalid locale selector combinations return a message with an empty `details` array.
+
+
+
+ Moving from `POST /items` or `POST /items/bulk`? See [Creating collection items](/data/docs/working-with-the-cms/create-items) for request mappings.
+
+ Rich Text fields can include Webflow components. See [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for markup and constraints.
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ items : typing.Sequence[InsertCollectionItem]
+ The items to create. A request can create up to 100 items, counted as the sum of the locales across every entry. An `allCmsLocales: true` entry counts once per current site locale; a new-item entry with neither locale selector counts as one.
+
+ skip_invalid_files : typing.Optional[bool]
+ When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[ItemsInsertItemsResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"collections/{jsonable_encoder(collection_id)}/items/insert",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ params={
+ "skipInvalidFiles": skip_invalid_files,
+ },
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[InsertCollectionItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ItemsInsertItemsResponse,
+ parse_obj_as(
+ type_=ItemsInsertItemsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
def get_item(
self,
collection_id: str,
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection Item.
Required scope | `CMS:read`
@@ -1256,7 +1734,20 @@ def get_item(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1272,6 +1763,7 @@ def get_item(
method="GET",
params={
"cmsLocaleId": cms_locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -1473,8 +1965,21 @@ def update_item(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected Item in a Collection.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -1507,7 +2012,12 @@ def update_item(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -1624,9 +2134,14 @@ def get_item_live(
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection live Item.
@@ -1644,7 +2159,20 @@ def get_item_live(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1660,6 +2188,7 @@ def get_item_live(
method="GET",
params={
"cmsLocaleId": cms_locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -1863,6 +2392,10 @@ def update_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
Required scope | `CMS:write`
@@ -1897,7 +2430,12 @@ def update_item_live(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -2029,6 +2567,9 @@ def publish_item(
"""
Publish an item or multiple items.
+ Publishing a draft item automatically sets `isDraft` to `false`.
+ You don't need to update the item's draft status before calling this endpoint.
+
Required scope | `cms:write`
Parameters
@@ -2053,9 +2594,6 @@ def publish_item(
json=convert_and_respect_annotation_metadata(
object_=request, annotation=ItemsPublishItemRequest, direction="write"
),
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -2158,14 +2696,30 @@ async def list_items(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItemList]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List of all Items within a Collection.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -2188,15 +2742,84 @@ async def list_items(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -2215,11 +2838,24 @@ async def list_items(
"limit": limit,
"name": name,
"slug": slug,
+ "createdOn": convert_and_respect_annotation_metadata(
+ object_=created_on, annotation=ItemsListItemsRequestCreatedOn, direction="write"
+ ),
"lastPublished": convert_and_respect_annotation_metadata(
object_=last_published, annotation=ItemsListItemsRequestLastPublished, direction="write"
),
+ "lastUpdated": convert_and_respect_annotation_metadata(
+ object_=last_updated, annotation=ItemsListItemsRequestLastUpdated, direction="write"
+ ),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter,
+ annotation=typing.Dict[str, typing.Optional[ItemsListItemsRequestFilterValue]],
+ direction="write",
+ ),
"sortBy": sort_by,
"sortOrder": sort_order,
+ "sort": sort,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -2306,10 +2942,47 @@ async def create_item(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create Item(s) in a Collection.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which accepts both request shapes below and can create an item in several locales under one ID. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how each shape maps.
+
- To create items across multiple locales, please use [this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create an item in several locales under one ID, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items).
Required scope | `CMS:write`
@@ -2551,12 +3224,25 @@ async def update_items(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[ItemsUpdateItemsResponse]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single item or multiple items in a Collection.
The limit for this endpoint is 100 items.
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -2678,18 +3364,34 @@ async def list_items_live(
limit: typing.Optional[int] = None,
name: typing.Optional[str] = None,
slug: typing.Optional[str] = None,
+ created_on: typing.Optional[ItemsListItemsLiveRequestCreatedOn] = None,
last_published: typing.Optional[ItemsListItemsLiveRequestLastPublished] = None,
+ last_updated: typing.Optional[ItemsListItemsLiveRequestLastUpdated] = None,
+ filter: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]] = None,
sort_by: typing.Optional[ItemsListItemsLiveRequestSortBy] = None,
sort_order: typing.Optional[ItemsListItemsLiveRequestSortOrder] = None,
+ sort: typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItemList]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
List all published items in a collection.
Serving data to applications in real-time? Use the Content Delivery API at `api-cdn.webflow.com` for better performance. The CDN-backed endpoint is optimized for high-volume reads, while the Data API is designed for writes and management operations.
+
+ This endpoint supports:
+
+ - Custom `filter[...]` queries support up to 10 filter terms and 2 text-search terms per request.
+ - Custom `sort[...]` queries support up to 3 sort fields per request.
+
+
Required scope | `CMS:read`
Parameters
@@ -2712,15 +3414,84 @@ async def list_items_live(
slug : typing.Optional[str]
Filter by the exact slug of the item
+ created_on : typing.Optional[ItemsListItemsLiveRequestCreatedOn]
+ Filter by the creation date of the item(s)
+
last_published : typing.Optional[ItemsListItemsLiveRequestLastPublished]
Filter by the last published date of the item(s)
+ last_updated : typing.Optional[ItemsListItemsLiveRequestLastUpdated]
+ Filter by the last updated date of the item(s)
+
+ filter : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]]]
+ Filter collection items by custom field values. Use bracket notation:
+ `filter[][]=`.
+
+ Example: `filter[price][gte]=10&filter[price][lte]=100&filter[name][contains]=shirt`.
+
+ Filters are combined with AND. You can combine custom field filters with top-level filters such as `name`, `slug`, `createdOn`, `lastPublished`, and `lastUpdated`. OR logic and nested filter groups are not supported on GET requests.
+
+ More filter terms can increase request latency.
+
+ Supported operators by field type:
+
+ | Field type | Supported operators |
+ | --- | --- |
+ | `id` | `eq`, `ne`, `in`, `nin` |
+ | `PlainText` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Number` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Switch` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `DateTime` | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `exists` |
+ | `Email`, `Phone`, `Link` | `eq`, `ne`, `in`, `nin`, `contains`, `ncontains`, `exists` |
+ | `Color` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Reference` | `eq`, `ne`, `in`, `nin`, `exists` |
+ | `Option` | `eq`, `ne`, `in`, `nin` |
+ | `RichText`, `Image`, `MultiImage`, `VideoLink`, `MultiReference` | `exists` |
+
+ `contains` and `ncontains` are case-insensitive. `ncontains` also matches items where the field is empty or not set.
+
+ `exists=true` matches items where the field has a value. `exists=false` matches items where the field is missing or null. For `Switch` fields, `false` is still a set value.
+
+ Value formats:
+
+ | Field type | Value format |
+ | --- | --- |
+ | `Number` | A valid number, such as `10` or `12.5` |
+ | `Switch` | `true` or `false` |
+ | `DateTime` | ISO 8601 date-time string |
+ | `id`, `Reference` | 24-character item ID |
+ | `Option` | Option ID |
+ | `in`, `nin` | Comma-separated list, up to 100 values |
+
+ Invalid fields, invalid values, and operators that do not apply to a field type return a `400 BadArgument` response.
+
sort_by : typing.Optional[ItemsListItemsLiveRequestSortBy]
Sort results by the provided value
sort_order : typing.Optional[ItemsListItemsLiveRequestSortOrder]
Sorts the results by asc or desc
+ sort : typing.Optional[typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestSortValue]]]
+ Sort collection items by custom fields using bracket notation: `sort[]=`.
+
+ - Example: `sort[price]=desc`
+ - Multiple sort fields are applied in query-string order. When `sort[...]` is provided, it takes precedence over `sortBy` and `sortOrder`.
+ - Sortable field types: `PlainText`, `Email`, `Phone`, `Number`, `DateTime`, and `Switch`.
+ - Unknown fields, invalid sort directions, and non-sortable field types return a `400 BadArgument` response.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -2739,11 +3510,24 @@ async def list_items_live(
"limit": limit,
"name": name,
"slug": slug,
+ "createdOn": convert_and_respect_annotation_metadata(
+ object_=created_on, annotation=ItemsListItemsLiveRequestCreatedOn, direction="write"
+ ),
"lastPublished": convert_and_respect_annotation_metadata(
object_=last_published, annotation=ItemsListItemsLiveRequestLastPublished, direction="write"
),
+ "lastUpdated": convert_and_respect_annotation_metadata(
+ object_=last_updated, annotation=ItemsListItemsLiveRequestLastUpdated, direction="write"
+ ),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter,
+ annotation=typing.Dict[str, typing.Optional[ItemsListItemsLiveRequestFilterValue]],
+ direction="write",
+ ),
"sortBy": sort_by,
"sortOrder": sort_order,
+ "sort": sort,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -2830,11 +3614,43 @@ async def create_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
- Create item(s) in a collection that will be immediately published to the live site.
-
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
- To create items across multiple locales, [please use this endpoint.](/data/reference/cms/collection-items/staged-items/create-items)
+ Create item(s) in a collection that will be immediately published to the live site.
+ This endpoint accepts two request shapes, and a request must use one or the other:
+
+ - **Single item** — send `fieldData` at the top level. Set `cmsLocaleId` to create the item in a specific locale.
+ - **Multiple items** — send an `items` array with at least one entry. Each entry needs its own `fieldData`, and can set its own `cmsLocaleId`, `isDraft`, and `isArchived`. The API ignores any other property on an entry.
+
+ ```json
+ {
+ "items": [
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Senior Data Analyst",
+ "slug": "senior-data-analyst"
+ }
+ },
+ {
+ "isArchived": false,
+ "isDraft": false,
+ "fieldData": {
+ "name": "Product Manager",
+ "slug": "product-manager"
+ }
+ }
+ ]
+ }
+ ```
+
+ A request that carries both `fieldData` and `items` returns a `400`.
+
+ To create items in several locales, use [Create Items](/data/reference/cms/collection-items/staged-items/create-items) and then [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item). Create Items writes staged content only.
Required scope | `CMS:write`
@@ -3065,6 +3881,10 @@ async def update_items_live(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItemListNoPagination]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a single published item or multiple published items (up to 100) in a Collection
Items will only be updated in the primary locale, unless a `cmsLocaleId` is included in the request.
@@ -3204,8 +4024,16 @@ async def create_items(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[BulkCollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Create an item or multiple items in a CMS Collection across multiple corresponding locales.
+
+ This endpoint remains supported. New integrations should use [Create Items](/data/reference/cms/collection-items/staged-items/create-items), which does everything this endpoint does and can also add locale variants to an existing item. [This guide to creating items](/data/docs/working-with-the-cms/create-items) shows how the request shape maps.
+
+
- This endpoint can create up to 100 items in a request.
- If the `cmsLocaleIds` parameter is not included in the request, an item will only be created in the primary locale.
@@ -3335,15 +4163,193 @@ async def create_items(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ async def insert_items(
+ self,
+ collection_id: str,
+ *,
+ items: typing.Sequence[InsertCollectionItem],
+ skip_invalid_files: typing.Optional[bool] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[ItemsInsertItemsResponse]:
+ """
+ Create one or more staged items in a CMS Collection.
+
+ Send an `items` array with `fieldData` for each item. By default, new items are created in the primary locale. To choose other locales or add variants to an existing item, expand the options below.
+
+ - **Publishing:** Set `isDraft: false` to queue an item for the next site publish. Items are created as drafts (`isDraft: true`) by default. To publish individual items, use [Publish Collection Item(s)](/data/reference/cms/collection-items/staged-items/publish-item).
+ - **Request limit:** Up to 100 items, counting every locale variant across all entries. One item in three locales counts as three; an item in the primary locale only counts as one.
+
+ Required scope | `cms:write`
+
+
+
+ Each new item gets one ID. Its locale variants share that ID and the same `fieldData`.
+
+ - **Selected locales:** Set `cmsLocaleIds` to the locale IDs you want to create the item in.
+ - **All site locales:** Set `allCmsLocales: true` to include every current site locale, including the primary locale, without listing IDs.
+ - **Primary locale only:** Omit both `cmsLocaleIds` and `allCmsLocales`.
+
+ Use only one locale selector per entry. `allCmsLocales` is available only for new items and accepts only `true`; omit it instead of sending `false`.
+
+
+
+ Set `id` to an existing item's ID and provide the new locales in `cmsLocaleIds`. The variants reuse that ID. The item must exist and must not already have a variant in any requested locale.
+
+ Send the source content in `fieldData` as a starting point for translators. To provide different content per locale, send multiple entries with the same `id`, each with one locale in `cmsLocaleIds` and its own `fieldData`.
+
+
+
+ These requests return a `400`:
+
+ - Combining `allCmsLocales` with `cmsLocaleIds` (even an empty array) or `id`.
+ - Setting `allCmsLocales` to `false`, a string, or `null`.
+ - Sending unrecognized properties, including singular `cmsLocaleId`. Use `cmsLocaleIds` (plural).
+ - Repeating a locale within one entry, or the same `id`/locale pair across entries.
+
+ Check the response's `message` and `details` for validation errors. Some errors identify an entry, such as `items[2].cmsLocaleIds`. Invalid locale selector combinations return a message with an empty `details` array.
+
+
+
+ Moving from `POST /items` or `POST /items/bulk`? See [Creating collection items](/data/docs/working-with-the-cms/create-items) for request mappings.
+
+ Rich Text fields can include Webflow components. See [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for markup and constraints.
+
+ Parameters
+ ----------
+ collection_id : str
+ Unique identifier for a Collection
+
+ items : typing.Sequence[InsertCollectionItem]
+ The items to create. A request can create up to 100 items, counted as the sum of the locales across every entry. An `allCmsLocales: true` entry counts once per current site locale; a new-item entry with neither locale selector counts as one.
+
+ skip_invalid_files : typing.Optional[bool]
+ When true, invalid files are skipped and processing continues. When false, the entire request fails if any file is invalid.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[ItemsInsertItemsResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"collections/{jsonable_encoder(collection_id)}/items/insert",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ params={
+ "skipInvalidFiles": skip_invalid_files,
+ },
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[InsertCollectionItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ ItemsInsertItemsResponse,
+ parse_obj_as(
+ type_=ItemsInsertItemsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
async def get_item(
self,
collection_id: str,
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection Item.
Required scope | `CMS:read`
@@ -3357,7 +4363,20 @@ async def get_item(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -3373,6 +4392,7 @@ async def get_item(
method="GET",
params={
"cmsLocaleId": cms_locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -3574,8 +4594,21 @@ async def update_item(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected Item in a Collection.
+
+ `isDraft: true` doesn't unpublish an item. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** the item gets a `Draft` status.
+ - **Already-published item:** the item gets a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: false` queues the item to publish on the next site publish. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live). For the full status mapping, see [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing).
+
+
Required scope | `CMS:write`
Parameters
@@ -3608,7 +4641,12 @@ async def update_item(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -3725,9 +4763,14 @@ async def get_item_live(
item_id: str,
*,
cms_locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Get details of a selected Collection live Item.
@@ -3745,7 +4788,20 @@ async def get_item_live(
Unique identifier for an Item
cms_locale_id : typing.Optional[str]
- Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. To query multiple locales, input a comma separated string.
+ Unique identifier for a CMS Locale. This UID is different from the Site locale identifier and is listed as `cmsLocaleId` in the Sites response. This endpoint returns a single item, so it accepts one locale. To retrieve an item in several locales, use [List Collection Items](/data/reference/cms/collection-items/staged-items/list-items) with `filter[id][eq]` and a comma separated `cmsLocaleId`.
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -3761,6 +4817,7 @@ async def get_item_live(
method="GET",
params={
"cmsLocaleId": cms_locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -3964,6 +5021,10 @@ async def update_item_live(
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[CollectionItem]:
"""
+
+ Rich Text field values can contain Webflow component instances as `` markup — see [Components in Rich Text](/data/docs/working-with-the-cms/components-in-rich-text) for the markup grammar, how to find component and property IDs, and the write constraints.
+
+
Update a selected live Item in a Collection. The updates for this Item will be published to the live site.
Required scope | `CMS:write`
@@ -3998,7 +5059,12 @@ async def update_item_live(
Boolean determining if the Item is set to archived
is_draft : typing.Optional[bool]
- Boolean determining if the Item is set to draft
+ Sets the item's draft state. The resulting status depends on whether the item has been published before:
+
+ - **Item that has never been published:** `isDraft: true` results in a `Draft` status.
+ - **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.
+
+ Setting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).
field_data : typing.Optional[CollectionItemPatchSingleFieldData]
@@ -4130,6 +5196,9 @@ async def publish_item(
"""
Publish an item or multiple items.
+ Publishing a draft item automatically sets `isDraft` to `false`.
+ You don't need to update the item's draft status before calling this endpoint.
+
Required scope | `cms:write`
Parameters
@@ -4154,9 +5223,6 @@ async def publish_item(
json=convert_and_respect_annotation_metadata(
object_=request, annotation=ItemsPublishItemRequest, direction="write"
),
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/collections/resources/items/types/__init__.py b/src/webflow/resources/collections/resources/items/types/__init__.py
index 6350593f..1430f963 100644
--- a/src/webflow/resources/collections/resources/items/types/__init__.py
+++ b/src/webflow/resources/collections/resources/items/types/__init__.py
@@ -16,10 +16,21 @@
from .items_create_item_request_body import ItemsCreateItemRequestBody
from .items_delete_items_live_request_items_item import ItemsDeleteItemsLiveRequestItemsItem
from .items_delete_items_request_items_item import ItemsDeleteItemsRequestItemsItem
+ from .items_insert_items_response import ItemsInsertItemsResponse
+ from .items_list_items_live_request_filter_value import ItemsListItemsLiveRequestFilterValue
+ from .items_list_items_live_request_filter_value_exists import ItemsListItemsLiveRequestFilterValueExists
+ from .items_list_items_live_request_filter_value_in import ItemsListItemsLiveRequestFilterValueIn
+ from .items_list_items_live_request_filter_value_nin import ItemsListItemsLiveRequestFilterValueNin
from .items_list_items_live_request_sort_by import ItemsListItemsLiveRequestSortBy
from .items_list_items_live_request_sort_order import ItemsListItemsLiveRequestSortOrder
+ from .items_list_items_live_request_sort_value import ItemsListItemsLiveRequestSortValue
+ from .items_list_items_request_filter_value import ItemsListItemsRequestFilterValue
+ from .items_list_items_request_filter_value_exists import ItemsListItemsRequestFilterValueExists
+ from .items_list_items_request_filter_value_in import ItemsListItemsRequestFilterValueIn
+ from .items_list_items_request_filter_value_nin import ItemsListItemsRequestFilterValueNin
from .items_list_items_request_sort_by import ItemsListItemsRequestSortBy
from .items_list_items_request_sort_order import ItemsListItemsRequestSortOrder
+ from .items_list_items_request_sort_value import ItemsListItemsRequestSortValue
from .items_publish_item_request import ItemsPublishItemRequest
from .items_publish_item_request_items_items_item import ItemsPublishItemRequestItemsItemsItem
from .items_publish_item_response import ItemsPublishItemResponse
@@ -36,10 +47,21 @@
"ItemsCreateItemRequestBody": ".items_create_item_request_body",
"ItemsDeleteItemsLiveRequestItemsItem": ".items_delete_items_live_request_items_item",
"ItemsDeleteItemsRequestItemsItem": ".items_delete_items_request_items_item",
+ "ItemsInsertItemsResponse": ".items_insert_items_response",
+ "ItemsListItemsLiveRequestFilterValue": ".items_list_items_live_request_filter_value",
+ "ItemsListItemsLiveRequestFilterValueExists": ".items_list_items_live_request_filter_value_exists",
+ "ItemsListItemsLiveRequestFilterValueIn": ".items_list_items_live_request_filter_value_in",
+ "ItemsListItemsLiveRequestFilterValueNin": ".items_list_items_live_request_filter_value_nin",
"ItemsListItemsLiveRequestSortBy": ".items_list_items_live_request_sort_by",
"ItemsListItemsLiveRequestSortOrder": ".items_list_items_live_request_sort_order",
+ "ItemsListItemsLiveRequestSortValue": ".items_list_items_live_request_sort_value",
+ "ItemsListItemsRequestFilterValue": ".items_list_items_request_filter_value",
+ "ItemsListItemsRequestFilterValueExists": ".items_list_items_request_filter_value_exists",
+ "ItemsListItemsRequestFilterValueIn": ".items_list_items_request_filter_value_in",
+ "ItemsListItemsRequestFilterValueNin": ".items_list_items_request_filter_value_nin",
"ItemsListItemsRequestSortBy": ".items_list_items_request_sort_by",
"ItemsListItemsRequestSortOrder": ".items_list_items_request_sort_order",
+ "ItemsListItemsRequestSortValue": ".items_list_items_request_sort_value",
"ItemsPublishItemRequest": ".items_publish_item_request",
"ItemsPublishItemRequestItemsItemsItem": ".items_publish_item_request_items_items_item",
"ItemsPublishItemResponse": ".items_publish_item_response",
@@ -80,10 +102,21 @@ def __dir__():
"ItemsCreateItemRequestBody",
"ItemsDeleteItemsLiveRequestItemsItem",
"ItemsDeleteItemsRequestItemsItem",
+ "ItemsInsertItemsResponse",
+ "ItemsListItemsLiveRequestFilterValue",
+ "ItemsListItemsLiveRequestFilterValueExists",
+ "ItemsListItemsLiveRequestFilterValueIn",
+ "ItemsListItemsLiveRequestFilterValueNin",
"ItemsListItemsLiveRequestSortBy",
"ItemsListItemsLiveRequestSortOrder",
+ "ItemsListItemsLiveRequestSortValue",
+ "ItemsListItemsRequestFilterValue",
+ "ItemsListItemsRequestFilterValueExists",
+ "ItemsListItemsRequestFilterValueIn",
+ "ItemsListItemsRequestFilterValueNin",
"ItemsListItemsRequestSortBy",
"ItemsListItemsRequestSortOrder",
+ "ItemsListItemsRequestSortValue",
"ItemsPublishItemRequest",
"ItemsPublishItemRequestItemsItemsItem",
"ItemsPublishItemResponse",
diff --git a/src/webflow/resources/collections/resources/items/types/items_delete_items_live_request_items_item.py b/src/webflow/resources/collections/resources/items/types/items_delete_items_live_request_items_item.py
index 0fa1b7b8..1cf345e2 100644
--- a/src/webflow/resources/collections/resources/items/types/items_delete_items_live_request_items_item.py
+++ b/src/webflow/resources/collections/resources/items/types/items_delete_items_live_request_items_item.py
@@ -18,7 +18,7 @@ class ItemsDeleteItemsLiveRequestItemsItem(UniversalBaseModel):
typing.Optional[typing.List[str]],
FieldMetadata(alias="cmsLocaleIds"),
pydantic.Field(
- alias="cmsLocaleIds", description="Array of identifiers for the locales where the item will be created"
+ alias="cmsLocaleIds", description="Array of identifiers for the locales where the item will be unpublished"
),
] = None
diff --git a/src/webflow/resources/collections/resources/items/types/items_delete_items_request_items_item.py b/src/webflow/resources/collections/resources/items/types/items_delete_items_request_items_item.py
index 5438d654..cd0526b4 100644
--- a/src/webflow/resources/collections/resources/items/types/items_delete_items_request_items_item.py
+++ b/src/webflow/resources/collections/resources/items/types/items_delete_items_request_items_item.py
@@ -18,7 +18,7 @@ class ItemsDeleteItemsRequestItemsItem(UniversalBaseModel):
typing.Optional[typing.List[str]],
FieldMetadata(alias="cmsLocaleIds"),
pydantic.Field(
- alias="cmsLocaleIds", description="Array of identifiers for the locales where the item will be created"
+ alias="cmsLocaleIds", description="Array of identifiers for the locales where the item will be deleted"
),
] = None
diff --git a/src/webflow/resources/collections/resources/items/types/items_insert_items_response.py b/src/webflow/resources/collections/resources/items/types/items_insert_items_response.py
new file mode 100644
index 00000000..b08207d0
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_insert_items_response.py
@@ -0,0 +1,23 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ......core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ......types.inserted_collection_item import InsertedCollectionItem
+
+
+class ItemsInsertItemsResponse(UniversalBaseModel):
+ items: typing.List[InsertedCollectionItem] = pydantic.Field()
+ """
+ The created items. Locale variants of the same item share an `id` and differ by `cmsLocaleId`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value.py
new file mode 100644
index 00000000..7bd91aeb
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ......core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ......core.serialization import FieldMetadata
+from .items_list_items_live_request_filter_value_exists import ItemsListItemsLiveRequestFilterValueExists
+from .items_list_items_live_request_filter_value_in import ItemsListItemsLiveRequestFilterValueIn
+from .items_list_items_live_request_filter_value_nin import ItemsListItemsLiveRequestFilterValueNin
+
+
+class ItemsListItemsLiveRequestFilterValue(UniversalBaseModel):
+ eq: typing.Optional[str] = None
+ ne: typing.Optional[str] = None
+ gt: typing.Optional[str] = None
+ gte: typing.Optional[str] = None
+ lt: typing.Optional[str] = None
+ lte: typing.Optional[str] = None
+ in_: typing_extensions.Annotated[
+ typing.Optional[ItemsListItemsLiveRequestFilterValueIn], FieldMetadata(alias="in"), pydantic.Field(alias="in")
+ ] = None
+ nin: typing.Optional[ItemsListItemsLiveRequestFilterValueNin] = None
+ contains: typing.Optional[str] = None
+ ncontains: typing.Optional[str] = None
+ exists: typing.Optional[ItemsListItemsLiveRequestFilterValueExists] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_exists.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_exists.py
new file mode 100644
index 00000000..27082276
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_exists.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsLiveRequestFilterValueExists = typing.Union[typing.Literal["true", "false"], typing.Any]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_in.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_in.py
new file mode 100644
index 00000000..81d44168
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_in.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsLiveRequestFilterValueIn = typing.Union[str, typing.List[str]]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_nin.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_nin.py
new file mode 100644
index 00000000..f709edc5
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_filter_value_nin.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsLiveRequestFilterValueNin = typing.Union[str, typing.List[str]]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_by.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_by.py
index 99294808..655f3083 100644
--- a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_by.py
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_by.py
@@ -2,4 +2,6 @@
import typing
-ItemsListItemsLiveRequestSortBy = typing.Union[typing.Literal["lastPublished", "name", "slug"], typing.Any]
+ItemsListItemsLiveRequestSortBy = typing.Union[
+ typing.Literal["createdOn", "lastPublished", "lastUpdated", "name", "slug"], typing.Any
+]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_value.py b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_value.py
new file mode 100644
index 00000000..c39a3ace
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_live_request_sort_value.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsLiveRequestSortValue = typing.Union[typing.Literal["asc", "desc"], typing.Any]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value.py
new file mode 100644
index 00000000..6c134dff
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ......core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ......core.serialization import FieldMetadata
+from .items_list_items_request_filter_value_exists import ItemsListItemsRequestFilterValueExists
+from .items_list_items_request_filter_value_in import ItemsListItemsRequestFilterValueIn
+from .items_list_items_request_filter_value_nin import ItemsListItemsRequestFilterValueNin
+
+
+class ItemsListItemsRequestFilterValue(UniversalBaseModel):
+ eq: typing.Optional[str] = None
+ ne: typing.Optional[str] = None
+ gt: typing.Optional[str] = None
+ gte: typing.Optional[str] = None
+ lt: typing.Optional[str] = None
+ lte: typing.Optional[str] = None
+ in_: typing_extensions.Annotated[
+ typing.Optional[ItemsListItemsRequestFilterValueIn], FieldMetadata(alias="in"), pydantic.Field(alias="in")
+ ] = None
+ nin: typing.Optional[ItemsListItemsRequestFilterValueNin] = None
+ contains: typing.Optional[str] = None
+ ncontains: typing.Optional[str] = None
+ exists: typing.Optional[ItemsListItemsRequestFilterValueExists] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_exists.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_exists.py
new file mode 100644
index 00000000..c4ca163b
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_exists.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsRequestFilterValueExists = typing.Union[typing.Literal["true", "false"], typing.Any]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_in.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_in.py
new file mode 100644
index 00000000..1676029c
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_in.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsRequestFilterValueIn = typing.Union[str, typing.List[str]]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_nin.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_nin.py
new file mode 100644
index 00000000..219a99eb
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_filter_value_nin.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsRequestFilterValueNin = typing.Union[str, typing.List[str]]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_by.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_by.py
index 222d1ff6..1564f0e2 100644
--- a/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_by.py
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_by.py
@@ -2,4 +2,6 @@
import typing
-ItemsListItemsRequestSortBy = typing.Union[typing.Literal["lastPublished", "name", "slug"], typing.Any]
+ItemsListItemsRequestSortBy = typing.Union[
+ typing.Literal["createdOn", "lastPublished", "lastUpdated", "name", "slug"], typing.Any
+]
diff --git a/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_value.py b/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_value.py
new file mode 100644
index 00000000..f598475a
--- /dev/null
+++ b/src/webflow/resources/collections/resources/items/types/items_list_items_request_sort_value.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ItemsListItemsRequestSortValue = typing.Union[typing.Literal["asc", "desc"], typing.Any]
diff --git a/src/webflow/resources/collections/resources/items/types/multiple_items.py b/src/webflow/resources/collections/resources/items/types/multiple_items.py
index 44eed570..4c845164 100644
--- a/src/webflow/resources/collections/resources/items/types/multiple_items.py
+++ b/src/webflow/resources/collections/resources/items/types/multiple_items.py
@@ -10,7 +10,7 @@
class MultipleItems(UniversalBaseModel):
items: typing.Optional[typing.List[CollectionItemPostSingle]] = pydantic.Field(default=None)
"""
- An array of items to create
+ The items to create. Each entry needs its own `fieldData`. Don't send a top-level `fieldData` with this shape.
"""
if IS_PYDANTIC_V2:
diff --git a/src/webflow/resources/collections/resources/items/types/multiple_live_items.py b/src/webflow/resources/collections/resources/items/types/multiple_live_items.py
index 8661342a..415aad00 100644
--- a/src/webflow/resources/collections/resources/items/types/multiple_live_items.py
+++ b/src/webflow/resources/collections/resources/items/types/multiple_live_items.py
@@ -10,7 +10,7 @@
class MultipleLiveItems(UniversalBaseModel):
items: typing.Optional[typing.List[CollectionItem]] = pydantic.Field(default=None)
"""
- List of collection items to create
+ The items to create. Each entry needs its own `fieldData`. Don't send a top-level `fieldData` with this shape.
"""
if IS_PYDANTIC_V2:
diff --git a/src/webflow/resources/components/client.py b/src/webflow/resources/components/client.py
index a9584d80..d4ff0501 100644
--- a/src/webflow/resources/components/client.py
+++ b/src/webflow/resources/components/client.py
@@ -96,6 +96,7 @@ def get_content(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> ComponentDom:
"""
@@ -117,7 +118,7 @@ def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -128,6 +129,19 @@ def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -150,6 +164,7 @@ def get_content(
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.get_content(
@@ -159,6 +174,7 @@ def get_content(
branch_id=branch_id,
limit=limit,
offset=offset,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -201,7 +217,7 @@ def update_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -301,6 +317,7 @@ def get_properties(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> ComponentProperties:
"""
@@ -321,7 +338,7 @@ def get_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -332,6 +349,19 @@ def get_properties(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -354,6 +384,7 @@ def get_properties(
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.get_properties(
@@ -363,6 +394,7 @@ def get_properties(
branch_id=branch_id,
limit=limit,
offset=offset,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -402,7 +434,7 @@ def update_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -538,6 +570,7 @@ async def get_content(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> ComponentDom:
"""
@@ -559,7 +592,7 @@ async def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -570,6 +603,19 @@ async def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -597,6 +643,7 @@ async def main() -> None:
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
@@ -609,6 +656,7 @@ async def main() -> None:
branch_id=branch_id,
limit=limit,
offset=offset,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -651,7 +699,7 @@ async def update_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -759,6 +807,7 @@ async def get_properties(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> ComponentProperties:
"""
@@ -779,7 +828,7 @@ async def get_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -790,6 +839,19 @@ async def get_properties(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -817,6 +879,7 @@ async def main() -> None:
branch_id="68026fa68ef6dc744c75b833",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
@@ -829,6 +892,7 @@ async def main() -> None:
branch_id=branch_id,
limit=limit,
offset=offset,
+ translatable=translatable,
request_options=request_options,
)
return _response.data
@@ -868,7 +932,7 @@ async def update_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
diff --git a/src/webflow/resources/components/raw_client.py b/src/webflow/resources/components/raw_client.py
index caf0799b..bd37bb0a 100644
--- a/src/webflow/resources/components/raw_client.py
+++ b/src/webflow/resources/components/raw_client.py
@@ -165,6 +165,7 @@ def get_content(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[ComponentDom]:
"""
@@ -186,7 +187,7 @@ def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -197,6 +198,19 @@ def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -214,6 +228,7 @@ def get_content(
"branchId": branch_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -329,7 +344,7 @@ def update_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -455,6 +470,7 @@ def get_properties(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[ComponentProperties]:
"""
@@ -475,7 +491,7 @@ def get_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -486,6 +502,19 @@ def get_properties(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -503,6 +532,7 @@ def get_properties(
"branchId": branch_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -615,7 +645,7 @@ def update_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -858,6 +888,7 @@ async def get_content(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[ComponentDom]:
"""
@@ -879,7 +910,7 @@ async def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -890,6 +921,19 @@ async def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -907,6 +951,7 @@ async def get_content(
"branchId": branch_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -1022,7 +1067,7 @@ async def update_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -1148,6 +1193,7 @@ async def get_properties(
branch_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[ComponentProperties]:
"""
@@ -1168,7 +1214,7 @@ async def get_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
@@ -1179,6 +1225,19 @@ async def get_properties(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1196,6 +1255,7 @@ async def get_properties(
"branchId": branch_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -1308,7 +1368,7 @@ async def update_properties(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
branch_id : typing.Optional[str]
Scope the operation to work on a specific branch.
diff --git a/src/webflow/resources/custom_fonts/__init__.py b/src/webflow/resources/custom_fonts/__init__.py
new file mode 100644
index 00000000..15c217c5
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/__init__.py
@@ -0,0 +1,58 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ CustomFontBatchCreateRequestItemsItem,
+ CustomFontBatchCreateRequestItemsItemFontDisplay,
+ CustomFontBatchDeleteRequestItemsItem,
+ CustomFontsCreateRequestFontDisplay,
+ CustomFontsGetResponse,
+ CustomFontsUpdateRequestFontDisplay,
+ CustomFontsUpdateResponse,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "CustomFontBatchCreateRequestItemsItem": ".types",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay": ".types",
+ "CustomFontBatchDeleteRequestItemsItem": ".types",
+ "CustomFontsCreateRequestFontDisplay": ".types",
+ "CustomFontsGetResponse": ".types",
+ "CustomFontsUpdateRequestFontDisplay": ".types",
+ "CustomFontsUpdateResponse": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "CustomFontBatchCreateRequestItemsItem",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay",
+ "CustomFontBatchDeleteRequestItemsItem",
+ "CustomFontsCreateRequestFontDisplay",
+ "CustomFontsGetResponse",
+ "CustomFontsUpdateRequestFontDisplay",
+ "CustomFontsUpdateResponse",
+]
diff --git a/src/webflow/resources/custom_fonts/client.py b/src/webflow/resources/custom_fonts/client.py
new file mode 100644
index 00000000..e464b799
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/client.py
@@ -0,0 +1,1047 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.request_options import RequestOptions
+from ...types.custom_font_axis import CustomFontAxis
+from ...types.custom_font_batch_create_response import CustomFontBatchCreateResponse
+from ...types.custom_font_batch_delete_response import CustomFontBatchDeleteResponse
+from ...types.custom_font_create_response import CustomFontCreateResponse
+from ...types.custom_fonts import CustomFonts
+from .raw_client import AsyncRawCustomFontsClient, RawCustomFontsClient
+from .types.custom_font_batch_create_request_items_item import CustomFontBatchCreateRequestItemsItem
+from .types.custom_font_batch_delete_request_items_item import CustomFontBatchDeleteRequestItemsItem
+from .types.custom_fonts_create_request_font_display import CustomFontsCreateRequestFontDisplay
+from .types.custom_fonts_get_response import CustomFontsGetResponse
+from .types.custom_fonts_update_request_font_display import CustomFontsUpdateRequestFontDisplay
+from .types.custom_fonts_update_response import CustomFontsUpdateResponse
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class CustomFontsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawCustomFontsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawCustomFontsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawCustomFontsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFonts:
+ """
+ List the custom fonts uploaded to a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ offset : typing.Optional[int]
+ Offset used for pagination if the results have more than limit records
+
+ limit : typing.Optional[int]
+ Maximum number of records to be returned (max limit: 100)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFonts
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
+ )
+ """
+ _response = self._raw_client.list(site_id, offset=offset, limit=limit, request_options=request_options)
+ return _response.data
+
+ def create(
+ self,
+ site_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ font_family: str,
+ weight: int,
+ italic: bool,
+ font_display: CustomFontsCreateRequestFontDisplay,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontCreateResponse:
+ """
+ Register a custom font on a site and get a presigned S3 URL to upload the font binary.
+
+ The response includes a `customFont` object and an `upload` object. Use the `upload.url` and `upload.fields`
+ to POST the font binary directly to S3 as `multipart/form-data`. The binary must go in a field named `file`
+ and must be the last field in the form (an AWS S3 requirement). S3 returns `201 Created` on a successful upload.
+
+ To learn more, see [Custom fonts](/data/docs/custom-fonts).
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ font_family : str
+ The CSS font-family name (1-256 characters). Commas are stripped server-side.
+
+ weight : int
+ CSS font-weight value (1-1000)
+
+ italic : bool
+ Whether the font is italic
+
+ font_display : CustomFontsCreateRequestFontDisplay
+ CSS font-display value
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes. Omit or pass an empty array for static fonts.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontCreateResponse
+ Font registered. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.create(
+ site_id="580e63e98c9a982ac9b8b741",
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
+ )
+ """
+ _response = self._raw_client.create(
+ site_id,
+ file_name=file_name,
+ file_hash=file_hash,
+ font_family=font_family,
+ weight=weight,
+ italic=italic,
+ font_display=font_display,
+ axes=axes,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def get(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> CustomFontsGetResponse:
+ """
+ Get details about a custom font on a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontsGetResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ """
+ _response = self._raw_client.get(site_id, font_id, request_options=request_options)
+ return _response.data
+
+ def delete(self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
+ """
+ Delete a custom font from a site.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ """
+ _response = self._raw_client.delete(site_id, font_id, request_options=request_options)
+ return _response.data
+
+ def update(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ font_family: typing.Optional[str] = OMIT,
+ weight: typing.Optional[int] = OMIT,
+ italic: typing.Optional[bool] = OMIT,
+ font_display: typing.Optional[CustomFontsUpdateRequestFontDisplay] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontsUpdateResponse:
+ """
+ Update the metadata of a custom font. The font binary is not changed by this endpoint.
+ To replace the binary, use [Replace custom font file](#operation/replace-custom-font-file).
+
+ The request body must include at least one of `fontFamily`, `weight`, `italic`, or `fontDisplay`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ font_family : typing.Optional[str]
+ The CSS font-family name (1-256 characters)
+
+ weight : typing.Optional[int]
+ CSS font-weight value (1-1000)
+
+ italic : typing.Optional[bool]
+ Whether the font is italic
+
+ font_display : typing.Optional[CustomFontsUpdateRequestFontDisplay]
+ CSS font-display value
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontsUpdateResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.update(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ """
+ _response = self._raw_client.update(
+ site_id,
+ font_id,
+ font_family=font_family,
+ weight=weight,
+ italic=italic,
+ font_display=font_display,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def replace_file(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontCreateResponse:
+ """
+ Replace the binary of an existing custom font while preserving its ID and any references to it.
+ The upload handshake is identical to [Create custom font](#operation/create-custom-font).
+
+ If the existing font has a non-empty `axes` array (a variable font), you must include an `axes` field
+ in the request. Send `axes: []` to declare that the new binary is a static font, or send the new variable
+ axes to declare it is still variable. Omitting `axes` when the existing font is variable returns `400`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes for the replacement binary. Required when the existing font has a non-empty `axes` array.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontCreateResponse
+ File replacement initiated. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.replace_file(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ file_name="AcmeSans-Regular-v2.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ )
+ """
+ _response = self._raw_client.replace_file(
+ site_id, font_id, file_name=file_name, file_hash=file_hash, axes=axes, request_options=request_options
+ )
+ return _response.data
+
+ def batch_create(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchCreateRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontBatchCreateResponse:
+ """
+ Register 1–25 custom fonts in a single request and get a presigned S3 URL for each one.
+ This collapses the registration step for a whole font family (for example, Regular, Bold,
+ Italic, and Bold Italic) into one rate-limited request.
+
+ Registration is batched, but the binary uploads are not: the response contains one `upload`
+ object per registered font, and you must POST each font binary to its own presigned S3 URL
+ exactly as you would for [Create custom font](#operation/create-custom-font). The Webflow API
+ server never receives the raw font bytes.
+
+ The response is `200 OK` for a valid request body. Per-font results are reported in the
+ `created` and `failed` arrays. If the site's font limit is reached partway through the batch,
+ the fonts that still fit are registered and returned in `created`, while the rest appear in
+ `failed` with `name: "FontLimitReached"` — valid fonts are never discarded because a later
+ font in the same batch could not be registered. Each presigned URL expires approximately
+ 15 minutes after issuance, so upload the binaries promptly.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchCreateRequestItemsItem]
+ The custom fonts to register. Each item uses the same shape as the single-font create request.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontBatchCreateResponse
+ Request was successful. Check the `created` and `failed` arrays for per-item results.
+
+ Examples
+ --------
+ from webflow import Webflow
+ from webflow.resources.custom_fonts import CustomFontBatchCreateRequestItemsItem
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.batch_create(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchCreateRequestItemsItem(
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
+ )
+ ],
+ )
+ """
+ _response = self._raw_client.batch_create(site_id, items=items, request_options=request_options)
+ return _response.data
+
+ def batch_delete(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchDeleteRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontBatchDeleteResponse:
+ """
+ Delete 1-100 custom fonts in a single request. The response is always `200 OK` for a valid request body.
+ Per-font results are reported in the `deleted` and `failed` arrays.
+
+ The endpoint is idempotent: fonts that do not exist appear in `failed` with `name: "NotFound"` rather than
+ failing the entire request. You can safely retry a partial failure by re-sending only the IDs that did not
+ appear in `deleted`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchDeleteRequestItemsItem]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontBatchDeleteResponse
+ Request was successful. Check `deleted` and `failed` arrays for per-item results.
+
+ Examples
+ --------
+ from webflow import Webflow
+ from webflow.resources.custom_fonts import CustomFontBatchDeleteRequestItemsItem
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.custom_fonts.batch_delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchDeleteRequestItemsItem(
+ id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ ],
+ )
+ """
+ _response = self._raw_client.batch_delete(site_id, items=items, request_options=request_options)
+ return _response.data
+
+
+class AsyncCustomFontsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawCustomFontsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawCustomFontsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawCustomFontsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFonts:
+ """
+ List the custom fonts uploaded to a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ offset : typing.Optional[int]
+ Offset used for pagination if the results have more than limit records
+
+ limit : typing.Optional[int]
+ Maximum number of records to be returned (max limit: 100)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFonts
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ offset=1,
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(site_id, offset=offset, limit=limit, request_options=request_options)
+ return _response.data
+
+ async def create(
+ self,
+ site_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ font_family: str,
+ weight: int,
+ italic: bool,
+ font_display: CustomFontsCreateRequestFontDisplay,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontCreateResponse:
+ """
+ Register a custom font on a site and get a presigned S3 URL to upload the font binary.
+
+ The response includes a `customFont` object and an `upload` object. Use the `upload.url` and `upload.fields`
+ to POST the font binary directly to S3 as `multipart/form-data`. The binary must go in a field named `file`
+ and must be the last field in the form (an AWS S3 requirement). S3 returns `201 Created` on a successful upload.
+
+ To learn more, see [Custom fonts](/data/docs/custom-fonts).
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ font_family : str
+ The CSS font-family name (1-256 characters). Commas are stripped server-side.
+
+ weight : int
+ CSS font-weight value (1-1000)
+
+ italic : bool
+ Whether the font is italic
+
+ font_display : CustomFontsCreateRequestFontDisplay
+ CSS font-display value
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes. Omit or pass an empty array for static fonts.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontCreateResponse
+ Font registered. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.create(
+ site_id="580e63e98c9a982ac9b8b741",
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create(
+ site_id,
+ file_name=file_name,
+ file_hash=file_hash,
+ font_family=font_family,
+ weight=weight,
+ italic=italic,
+ font_display=font_display,
+ axes=axes,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def get(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> CustomFontsGetResponse:
+ """
+ Get details about a custom font on a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontsGetResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(site_id, font_id, request_options=request_options)
+ return _response.data
+
+ async def delete(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Delete a custom font from a site.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.delete(site_id, font_id, request_options=request_options)
+ return _response.data
+
+ async def update(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ font_family: typing.Optional[str] = OMIT,
+ weight: typing.Optional[int] = OMIT,
+ italic: typing.Optional[bool] = OMIT,
+ font_display: typing.Optional[CustomFontsUpdateRequestFontDisplay] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontsUpdateResponse:
+ """
+ Update the metadata of a custom font. The font binary is not changed by this endpoint.
+ To replace the binary, use [Replace custom font file](#operation/replace-custom-font-file).
+
+ The request body must include at least one of `fontFamily`, `weight`, `italic`, or `fontDisplay`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ font_family : typing.Optional[str]
+ The CSS font-family name (1-256 characters)
+
+ weight : typing.Optional[int]
+ CSS font-weight value (1-1000)
+
+ italic : typing.Optional[bool]
+ Whether the font is italic
+
+ font_display : typing.Optional[CustomFontsUpdateRequestFontDisplay]
+ CSS font-display value
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontsUpdateResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.update(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update(
+ site_id,
+ font_id,
+ font_family=font_family,
+ weight=weight,
+ italic=italic,
+ font_display=font_display,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def replace_file(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontCreateResponse:
+ """
+ Replace the binary of an existing custom font while preserving its ID and any references to it.
+ The upload handshake is identical to [Create custom font](#operation/create-custom-font).
+
+ If the existing font has a non-empty `axes` array (a variable font), you must include an `axes` field
+ in the request. Send `axes: []` to declare that the new binary is a static font, or send the new variable
+ axes to declare it is still variable. Omitting `axes` when the existing font is variable returns `400`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes for the replacement binary. Required when the existing font has a non-empty `axes` array.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontCreateResponse
+ File replacement initiated. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.replace_file(
+ site_id="580e63e98c9a982ac9b8b741",
+ font_id="66f3a1b2c4d5e6f7a8b9c0d1",
+ file_name="AcmeSans-Regular-v2.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.replace_file(
+ site_id, font_id, file_name=file_name, file_hash=file_hash, axes=axes, request_options=request_options
+ )
+ return _response.data
+
+ async def batch_create(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchCreateRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontBatchCreateResponse:
+ """
+ Register 1–25 custom fonts in a single request and get a presigned S3 URL for each one.
+ This collapses the registration step for a whole font family (for example, Regular, Bold,
+ Italic, and Bold Italic) into one rate-limited request.
+
+ Registration is batched, but the binary uploads are not: the response contains one `upload`
+ object per registered font, and you must POST each font binary to its own presigned S3 URL
+ exactly as you would for [Create custom font](#operation/create-custom-font). The Webflow API
+ server never receives the raw font bytes.
+
+ The response is `200 OK` for a valid request body. Per-font results are reported in the
+ `created` and `failed` arrays. If the site's font limit is reached partway through the batch,
+ the fonts that still fit are registered and returned in `created`, while the rest appear in
+ `failed` with `name: "FontLimitReached"` — valid fonts are never discarded because a later
+ font in the same batch could not be registered. Each presigned URL expires approximately
+ 15 minutes after issuance, so upload the binaries promptly.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchCreateRequestItemsItem]
+ The custom fonts to register. Each item uses the same shape as the single-font create request.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontBatchCreateResponse
+ Request was successful. Check the `created` and `failed` arrays for per-item results.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+ from webflow.resources.custom_fonts import CustomFontBatchCreateRequestItemsItem
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.batch_create(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchCreateRequestItemsItem(
+ file_name="AcmeSans-Regular.woff2",
+ file_hash="3c7d87c9575702bc3b1e991f4d3c638e",
+ font_family="Acme Sans",
+ weight=400,
+ italic=False,
+ font_display="auto",
+ )
+ ],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.batch_create(site_id, items=items, request_options=request_options)
+ return _response.data
+
+ async def batch_delete(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchDeleteRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CustomFontBatchDeleteResponse:
+ """
+ Delete 1-100 custom fonts in a single request. The response is always `200 OK` for a valid request body.
+ Per-font results are reported in the `deleted` and `failed` arrays.
+
+ The endpoint is idempotent: fonts that do not exist appear in `failed` with `name: "NotFound"` rather than
+ failing the entire request. You can safely retry a partial failure by re-sending only the IDs that did not
+ appear in `deleted`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchDeleteRequestItemsItem]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CustomFontBatchDeleteResponse
+ Request was successful. Check `deleted` and `failed` arrays for per-item results.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+ from webflow.resources.custom_fonts import CustomFontBatchDeleteRequestItemsItem
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.custom_fonts.batch_delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ items=[
+ CustomFontBatchDeleteRequestItemsItem(
+ id="66f3a1b2c4d5e6f7a8b9c0d1",
+ )
+ ],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.batch_delete(site_id, items=items, request_options=request_options)
+ return _response.data
diff --git a/src/webflow/resources/custom_fonts/raw_client.py b/src/webflow/resources/custom_fonts/raw_client.py
new file mode 100644
index 00000000..f03c188c
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/raw_client.py
@@ -0,0 +1,2277 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ...core.api_error import ApiError
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.http_response import AsyncHttpResponse, HttpResponse
+from ...core.jsonable_encoder import jsonable_encoder
+from ...core.parse_error import ParsingError
+from ...core.pydantic_utilities import parse_obj_as
+from ...core.request_options import RequestOptions
+from ...core.serialization import convert_and_respect_annotation_metadata
+from ...errors.bad_request_error import BadRequestError
+from ...errors.conflict_error import ConflictError
+from ...errors.forbidden_error import ForbiddenError
+from ...errors.internal_server_error import InternalServerError
+from ...errors.not_found_error import NotFoundError
+from ...errors.service_unavailable_error import ServiceUnavailableError
+from ...errors.too_many_requests_error import TooManyRequestsError
+from ...errors.unauthorized_error import UnauthorizedError
+from ...types.custom_font_axis import CustomFontAxis
+from ...types.custom_font_batch_create_response import CustomFontBatchCreateResponse
+from ...types.custom_font_batch_delete_response import CustomFontBatchDeleteResponse
+from ...types.custom_font_create_response import CustomFontCreateResponse
+from ...types.custom_fonts import CustomFonts
+from ...types.error import Error
+from .types.custom_font_batch_create_request_items_item import CustomFontBatchCreateRequestItemsItem
+from .types.custom_font_batch_delete_request_items_item import CustomFontBatchDeleteRequestItemsItem
+from .types.custom_fonts_create_request_font_display import CustomFontsCreateRequestFontDisplay
+from .types.custom_fonts_get_response import CustomFontsGetResponse
+from .types.custom_fonts_update_request_font_display import CustomFontsUpdateRequestFontDisplay
+from .types.custom_fonts_update_response import CustomFontsUpdateResponse
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawCustomFontsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFonts]:
+ """
+ List the custom fonts uploaded to a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ offset : typing.Optional[int]
+ Offset used for pagination if the results have more than limit records
+
+ limit : typing.Optional[int]
+ Maximum number of records to be returned (max limit: 100)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFonts]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "offset": offset,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFonts,
+ parse_obj_as(
+ type_=CustomFonts, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def create(
+ self,
+ site_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ font_family: str,
+ weight: int,
+ italic: bool,
+ font_display: CustomFontsCreateRequestFontDisplay,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFontCreateResponse]:
+ """
+ Register a custom font on a site and get a presigned S3 URL to upload the font binary.
+
+ The response includes a `customFont` object and an `upload` object. Use the `upload.url` and `upload.fields`
+ to POST the font binary directly to S3 as `multipart/form-data`. The binary must go in a field named `file`
+ and must be the last field in the form (an AWS S3 requirement). S3 returns `201 Created` on a successful upload.
+
+ To learn more, see [Custom fonts](/data/docs/custom-fonts).
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ font_family : str
+ The CSS font-family name (1-256 characters). Commas are stripped server-side.
+
+ weight : int
+ CSS font-weight value (1-1000)
+
+ italic : bool
+ Whether the font is italic
+
+ font_display : CustomFontsCreateRequestFontDisplay
+ CSS font-display value
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes. Omit or pass an empty array for static fonts.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontCreateResponse]
+ Font registered. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "fileName": file_name,
+ "fileHash": file_hash,
+ "fontFamily": font_family,
+ "weight": weight,
+ "italic": italic,
+ "fontDisplay": font_display,
+ "axes": convert_and_respect_annotation_metadata(
+ object_=axes, annotation=typing.Sequence[CustomFontAxis], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontCreateResponse,
+ parse_obj_as(
+ type_=CustomFontCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 503:
+ raise ServiceUnavailableError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[CustomFontsGetResponse]:
+ """
+ Get details about a custom font on a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontsGetResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontsGetResponse,
+ parse_obj_as(
+ type_=CustomFontsGetResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def delete(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Delete a custom font from a site.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def update(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ font_family: typing.Optional[str] = OMIT,
+ weight: typing.Optional[int] = OMIT,
+ italic: typing.Optional[bool] = OMIT,
+ font_display: typing.Optional[CustomFontsUpdateRequestFontDisplay] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFontsUpdateResponse]:
+ """
+ Update the metadata of a custom font. The font binary is not changed by this endpoint.
+ To replace the binary, use [Replace custom font file](#operation/replace-custom-font-file).
+
+ The request body must include at least one of `fontFamily`, `weight`, `italic`, or `fontDisplay`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ font_family : typing.Optional[str]
+ The CSS font-family name (1-256 characters)
+
+ weight : typing.Optional[int]
+ CSS font-weight value (1-1000)
+
+ italic : typing.Optional[bool]
+ Whether the font is italic
+
+ font_display : typing.Optional[CustomFontsUpdateRequestFontDisplay]
+ CSS font-display value
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontsUpdateResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "fontFamily": font_family,
+ "weight": weight,
+ "italic": italic,
+ "fontDisplay": font_display,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontsUpdateResponse,
+ parse_obj_as(
+ type_=CustomFontsUpdateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def replace_file(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFontCreateResponse]:
+ """
+ Replace the binary of an existing custom font while preserving its ID and any references to it.
+ The upload handshake is identical to [Create custom font](#operation/create-custom-font).
+
+ If the existing font has a non-empty `axes` array (a variable font), you must include an `axes` field
+ in the request. Send `axes: []` to declare that the new binary is a static font, or send the new variable
+ axes to declare it is still variable. Omitting `axes` when the existing font is variable returns `400`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes for the replacement binary. Required when the existing font has a non-empty `axes` array.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontCreateResponse]
+ File replacement initiated. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}/file",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PUT",
+ json={
+ "fileName": file_name,
+ "fileHash": file_hash,
+ "axes": convert_and_respect_annotation_metadata(
+ object_=axes, annotation=typing.Sequence[CustomFontAxis], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontCreateResponse,
+ parse_obj_as(
+ type_=CustomFontCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def batch_create(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchCreateRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFontBatchCreateResponse]:
+ """
+ Register 1–25 custom fonts in a single request and get a presigned S3 URL for each one.
+ This collapses the registration step for a whole font family (for example, Regular, Bold,
+ Italic, and Bold Italic) into one rate-limited request.
+
+ Registration is batched, but the binary uploads are not: the response contains one `upload`
+ object per registered font, and you must POST each font binary to its own presigned S3 URL
+ exactly as you would for [Create custom font](#operation/create-custom-font). The Webflow API
+ server never receives the raw font bytes.
+
+ The response is `200 OK` for a valid request body. Per-font results are reported in the
+ `created` and `failed` arrays. If the site's font limit is reached partway through the batch,
+ the fonts that still fit are registered and returned in `created`, while the rest appear in
+ `failed` with `name: "FontLimitReached"` — valid fonts are never discarded because a later
+ font in the same batch could not be registered. Each presigned URL expires approximately
+ 15 minutes after issuance, so upload the binaries promptly.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchCreateRequestItemsItem]
+ The custom fonts to register. Each item uses the same shape as the single-font create request.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontBatchCreateResponse]
+ Request was successful. Check the `created` and `failed` arrays for per-item results.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/batchCreate",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[CustomFontBatchCreateRequestItemsItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontBatchCreateResponse,
+ parse_obj_as(
+ type_=CustomFontBatchCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 503:
+ raise ServiceUnavailableError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def batch_delete(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchDeleteRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CustomFontBatchDeleteResponse]:
+ """
+ Delete 1-100 custom fonts in a single request. The response is always `200 OK` for a valid request body.
+ Per-font results are reported in the `deleted` and `failed` arrays.
+
+ The endpoint is idempotent: fonts that do not exist appear in `failed` with `name: "NotFound"` rather than
+ failing the entire request. You can safely retry a partial failure by re-sending only the IDs that did not
+ appear in `deleted`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchDeleteRequestItemsItem]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CustomFontBatchDeleteResponse]
+ Request was successful. Check `deleted` and `failed` arrays for per-item results.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/batchDelete",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[CustomFontBatchDeleteRequestItemsItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontBatchDeleteResponse,
+ parse_obj_as(
+ type_=CustomFontBatchDeleteResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawCustomFontsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ offset: typing.Optional[int] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFonts]:
+ """
+ List the custom fonts uploaded to a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ offset : typing.Optional[int]
+ Offset used for pagination if the results have more than limit records
+
+ limit : typing.Optional[int]
+ Maximum number of records to be returned (max limit: 100)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFonts]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "offset": offset,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFonts,
+ parse_obj_as(
+ type_=CustomFonts, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def create(
+ self,
+ site_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ font_family: str,
+ weight: int,
+ italic: bool,
+ font_display: CustomFontsCreateRequestFontDisplay,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFontCreateResponse]:
+ """
+ Register a custom font on a site and get a presigned S3 URL to upload the font binary.
+
+ The response includes a `customFont` object and an `upload` object. Use the `upload.url` and `upload.fields`
+ to POST the font binary directly to S3 as `multipart/form-data`. The binary must go in a field named `file`
+ and must be the last field in the form (an AWS S3 requirement). S3 returns `201 Created` on a successful upload.
+
+ To learn more, see [Custom fonts](/data/docs/custom-fonts).
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ font_family : str
+ The CSS font-family name (1-256 characters). Commas are stripped server-side.
+
+ weight : int
+ CSS font-weight value (1-1000)
+
+ italic : bool
+ Whether the font is italic
+
+ font_display : CustomFontsCreateRequestFontDisplay
+ CSS font-display value
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes. Omit or pass an empty array for static fonts.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontCreateResponse]
+ Font registered. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "fileName": file_name,
+ "fileHash": file_hash,
+ "fontFamily": font_family,
+ "weight": weight,
+ "italic": italic,
+ "fontDisplay": font_display,
+ "axes": convert_and_respect_annotation_metadata(
+ object_=axes, annotation=typing.Sequence[CustomFontAxis], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontCreateResponse,
+ parse_obj_as(
+ type_=CustomFontCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 503:
+ raise ServiceUnavailableError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[CustomFontsGetResponse]:
+ """
+ Get details about a custom font on a site.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontsGetResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontsGetResponse,
+ parse_obj_as(
+ type_=CustomFontsGetResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def delete(
+ self, site_id: str, font_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Delete a custom font from a site.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def update(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ font_family: typing.Optional[str] = OMIT,
+ weight: typing.Optional[int] = OMIT,
+ italic: typing.Optional[bool] = OMIT,
+ font_display: typing.Optional[CustomFontsUpdateRequestFontDisplay] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFontsUpdateResponse]:
+ """
+ Update the metadata of a custom font. The font binary is not changed by this endpoint.
+ To replace the binary, use [Replace custom font file](#operation/replace-custom-font-file).
+
+ The request body must include at least one of `fontFamily`, `weight`, `italic`, or `fontDisplay`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ font_family : typing.Optional[str]
+ The CSS font-family name (1-256 characters)
+
+ weight : typing.Optional[int]
+ CSS font-weight value (1-1000)
+
+ italic : typing.Optional[bool]
+ Whether the font is italic
+
+ font_display : typing.Optional[CustomFontsUpdateRequestFontDisplay]
+ CSS font-display value
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontsUpdateResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "fontFamily": font_family,
+ "weight": weight,
+ "italic": italic,
+ "fontDisplay": font_display,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontsUpdateResponse,
+ parse_obj_as(
+ type_=CustomFontsUpdateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def replace_file(
+ self,
+ site_id: str,
+ font_id: str,
+ *,
+ file_name: str,
+ file_hash: str,
+ axes: typing.Optional[typing.Sequence[CustomFontAxis]] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFontCreateResponse]:
+ """
+ Replace the binary of an existing custom font while preserving its ID and any references to it.
+ The upload handshake is identical to [Create custom font](#operation/create-custom-font).
+
+ If the existing font has a non-empty `axes` array (a variable font), you must include an `axes` field
+ in the request. Send `axes: []` to declare that the new binary is a static font, or send the new variable
+ axes to declare it is still variable. Omitting `axes` when the existing font is variable returns `400`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ font_id : str
+ Unique identifier for a custom font on a site
+
+ file_name : str
+ File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.
+
+ file_hash : str
+ Lowercase hex MD5 hash of the font binary (exactly 32 characters)
+
+ axes : typing.Optional[typing.Sequence[CustomFontAxis]]
+ Variable font axes for the replacement binary. Required when the existing font has a non-empty `axes` array.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontCreateResponse]
+ File replacement initiated. Upload the binary to the presigned S3 URL in `upload` to complete the process.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/{jsonable_encoder(font_id)}/file",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PUT",
+ json={
+ "fileName": file_name,
+ "fileHash": file_hash,
+ "axes": convert_and_respect_annotation_metadata(
+ object_=axes, annotation=typing.Sequence[CustomFontAxis], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontCreateResponse,
+ parse_obj_as(
+ type_=CustomFontCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def batch_create(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchCreateRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFontBatchCreateResponse]:
+ """
+ Register 1–25 custom fonts in a single request and get a presigned S3 URL for each one.
+ This collapses the registration step for a whole font family (for example, Regular, Bold,
+ Italic, and Bold Italic) into one rate-limited request.
+
+ Registration is batched, but the binary uploads are not: the response contains one `upload`
+ object per registered font, and you must POST each font binary to its own presigned S3 URL
+ exactly as you would for [Create custom font](#operation/create-custom-font). The Webflow API
+ server never receives the raw font bytes.
+
+ The response is `200 OK` for a valid request body. Per-font results are reported in the
+ `created` and `failed` arrays. If the site's font limit is reached partway through the batch,
+ the fonts that still fit are registered and returned in `created`, while the rest appear in
+ `failed` with `name: "FontLimitReached"` — valid fonts are never discarded because a later
+ font in the same batch could not be registered. Each presigned URL expires approximately
+ 15 minutes after issuance, so upload the binaries promptly.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchCreateRequestItemsItem]
+ The custom fonts to register. Each item uses the same shape as the single-font create request.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontBatchCreateResponse]
+ Request was successful. Check the `created` and `failed` arrays for per-item results.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/batchCreate",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[CustomFontBatchCreateRequestItemsItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontBatchCreateResponse,
+ parse_obj_as(
+ type_=CustomFontBatchCreateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 503:
+ raise ServiceUnavailableError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def batch_delete(
+ self,
+ site_id: str,
+ *,
+ items: typing.Sequence[CustomFontBatchDeleteRequestItemsItem],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CustomFontBatchDeleteResponse]:
+ """
+ Delete 1-100 custom fonts in a single request. The response is always `200 OK` for a valid request body.
+ Per-font results are reported in the `deleted` and `failed` arrays.
+
+ The endpoint is idempotent: fonts that do not exist appear in `failed` with `name: "NotFound"` rather than
+ failing the entire request. You can safely retry a partial failure by re-sending only the IDs that did not
+ appear in `deleted`.
+
+ Required scope | `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ items : typing.Sequence[CustomFontBatchDeleteRequestItemsItem]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CustomFontBatchDeleteResponse]
+ Request was successful. Check `deleted` and `failed` arrays for per-item results.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/custom_fonts/batchDelete",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "items": convert_and_respect_annotation_metadata(
+ object_=items, annotation=typing.Sequence[CustomFontBatchDeleteRequestItemsItem], direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CustomFontBatchDeleteResponse,
+ parse_obj_as(
+ type_=CustomFontBatchDeleteResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/custom_fonts/types/__init__.py b/src/webflow/resources/custom_fonts/types/__init__.py
new file mode 100644
index 00000000..b356199a
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/__init__.py
@@ -0,0 +1,58 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .custom_font_batch_create_request_items_item import CustomFontBatchCreateRequestItemsItem
+ from .custom_font_batch_create_request_items_item_font_display import (
+ CustomFontBatchCreateRequestItemsItemFontDisplay,
+ )
+ from .custom_font_batch_delete_request_items_item import CustomFontBatchDeleteRequestItemsItem
+ from .custom_fonts_create_request_font_display import CustomFontsCreateRequestFontDisplay
+ from .custom_fonts_get_response import CustomFontsGetResponse
+ from .custom_fonts_update_request_font_display import CustomFontsUpdateRequestFontDisplay
+ from .custom_fonts_update_response import CustomFontsUpdateResponse
+_dynamic_imports: typing.Dict[str, str] = {
+ "CustomFontBatchCreateRequestItemsItem": ".custom_font_batch_create_request_items_item",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay": ".custom_font_batch_create_request_items_item_font_display",
+ "CustomFontBatchDeleteRequestItemsItem": ".custom_font_batch_delete_request_items_item",
+ "CustomFontsCreateRequestFontDisplay": ".custom_fonts_create_request_font_display",
+ "CustomFontsGetResponse": ".custom_fonts_get_response",
+ "CustomFontsUpdateRequestFontDisplay": ".custom_fonts_update_request_font_display",
+ "CustomFontsUpdateResponse": ".custom_fonts_update_response",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "CustomFontBatchCreateRequestItemsItem",
+ "CustomFontBatchCreateRequestItemsItemFontDisplay",
+ "CustomFontBatchDeleteRequestItemsItem",
+ "CustomFontsCreateRequestFontDisplay",
+ "CustomFontsGetResponse",
+ "CustomFontsUpdateRequestFontDisplay",
+ "CustomFontsUpdateResponse",
+]
diff --git a/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item.py b/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item.py
new file mode 100644
index 00000000..9431abdf
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item.py
@@ -0,0 +1,64 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ....core.serialization import FieldMetadata
+from ....types.custom_font_axis import CustomFontAxis
+from .custom_font_batch_create_request_items_item_font_display import CustomFontBatchCreateRequestItemsItemFontDisplay
+
+
+class CustomFontBatchCreateRequestItemsItem(UniversalBaseModel):
+ file_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="fileName"),
+ pydantic.Field(
+ alias="fileName",
+ description="File name including extension. Accepted extensions are `.woff2`, `.woff`, `.ttf`, `.otf`, and `.eot`. Maximum 256 characters.",
+ ),
+ ]
+ file_hash: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="fileHash"),
+ pydantic.Field(
+ alias="fileHash", description="Lowercase hex MD5 hash of the font binary (exactly 32 characters)"
+ ),
+ ]
+ font_family: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="fontFamily"),
+ pydantic.Field(
+ alias="fontFamily",
+ description="The CSS font-family name (1-256 characters). Commas are stripped server-side.",
+ ),
+ ]
+ weight: int = pydantic.Field()
+ """
+ CSS font-weight value (1-1000)
+ """
+
+ italic: bool = pydantic.Field()
+ """
+ Whether the font is italic
+ """
+
+ font_display: typing_extensions.Annotated[
+ CustomFontBatchCreateRequestItemsItemFontDisplay,
+ FieldMetadata(alias="fontDisplay"),
+ pydantic.Field(alias="fontDisplay", description="CSS font-display value"),
+ ]
+ axes: typing.Optional[typing.List[CustomFontAxis]] = pydantic.Field(default=None)
+ """
+ Variable font axes. Omit or pass an empty array for static fonts.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item_font_display.py b/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item_font_display.py
new file mode 100644
index 00000000..660dc463
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_font_batch_create_request_items_item_font_display.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CustomFontBatchCreateRequestItemsItemFontDisplay = typing.Union[
+ typing.Literal["auto", "block", "swap", "fallback", "optional"], typing.Any
+]
diff --git a/src/webflow/resources/custom_fonts/types/custom_font_batch_delete_request_items_item.py b/src/webflow/resources/custom_fonts/types/custom_font_batch_delete_request_items_item.py
new file mode 100644
index 00000000..09eaf875
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_font_batch_delete_request_items_item.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CustomFontBatchDeleteRequestItemsItem(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ The ID of the custom font to delete
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/custom_fonts/types/custom_fonts_create_request_font_display.py b/src/webflow/resources/custom_fonts/types/custom_fonts_create_request_font_display.py
new file mode 100644
index 00000000..b403d841
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_fonts_create_request_font_display.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CustomFontsCreateRequestFontDisplay = typing.Union[
+ typing.Literal["auto", "block", "swap", "fallback", "optional"], typing.Any
+]
diff --git a/src/webflow/resources/custom_fonts/types/custom_fonts_get_response.py b/src/webflow/resources/custom_fonts/types/custom_fonts_get_response.py
new file mode 100644
index 00000000..a2a2d6db
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_fonts_get_response.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ....core.serialization import FieldMetadata
+from ....types.custom_font import CustomFont
+
+
+class CustomFontsGetResponse(UniversalBaseModel):
+ custom_font: typing_extensions.Annotated[
+ CustomFont, FieldMetadata(alias="customFont"), pydantic.Field(alias="customFont")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/custom_fonts/types/custom_fonts_update_request_font_display.py b/src/webflow/resources/custom_fonts/types/custom_fonts_update_request_font_display.py
new file mode 100644
index 00000000..82fc2dfb
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_fonts_update_request_font_display.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CustomFontsUpdateRequestFontDisplay = typing.Union[
+ typing.Literal["auto", "block", "swap", "fallback", "optional"], typing.Any
+]
diff --git a/src/webflow/resources/custom_fonts/types/custom_fonts_update_response.py b/src/webflow/resources/custom_fonts/types/custom_fonts_update_response.py
new file mode 100644
index 00000000..c8bc1a8a
--- /dev/null
+++ b/src/webflow/resources/custom_fonts/types/custom_fonts_update_response.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ....core.serialization import FieldMetadata
+from ....types.custom_font import CustomFont
+
+
+class CustomFontsUpdateResponse(UniversalBaseModel):
+ custom_font: typing_extensions.Annotated[
+ CustomFont, FieldMetadata(alias="customFont"), pydantic.Field(alias="customFont")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/resources/optimize/__init__.py b/src/webflow/resources/optimize/__init__.py
new file mode 100644
index 00000000..4b10424f
--- /dev/null
+++ b/src/webflow/resources/optimize/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .resources import optimizations
+_dynamic_imports: typing.Dict[str, str] = {"optimizations": ".resources"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["optimizations"]
diff --git a/src/webflow/resources/optimize/client.py b/src/webflow/resources/optimize/client.py
new file mode 100644
index 00000000..99794446
--- /dev/null
+++ b/src/webflow/resources/optimize/client.py
@@ -0,0 +1,145 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.request_options import RequestOptions
+from ...types.optimize_reference_list_response import OptimizeReferenceListResponse
+from .raw_client import AsyncRawOptimizeClient, RawOptimizeClient
+
+if typing.TYPE_CHECKING:
+ from .resources.optimizations.client import AsyncOptimizationsClient, OptimizationsClient
+
+
+class OptimizeClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawOptimizeClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._optimizations: typing.Optional[OptimizationsClient] = None
+
+ @property
+ def with_raw_response(self) -> RawOptimizeClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawOptimizeClient
+ """
+ return self._raw_client
+
+ def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> OptimizeReferenceListResponse:
+ """
+ Lists the `optimizeId` values the provided access token can access, and the resource each one comes from, such as a Webflow site or a non-Webflow site. Use an `optimizeId` with the other Optimize endpoints.
+
+ Results are sorted by `optimizeId` and aren't paginated.
+
+ Webflow sites accept OAuth app tokens and Site API tokens. Non-Webflow sites that use Optimize accept only OAuth app tokens, including tokens authorized for a whole workspace.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizeReferenceListResponse
+ The `optimizeId` values the access token can access.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.list()
+ """
+ _response = self._raw_client.list(request_options=request_options)
+ return _response.data
+
+ @property
+ def optimizations(self):
+ if self._optimizations is None:
+ from .resources.optimizations.client import OptimizationsClient # noqa: E402
+
+ self._optimizations = OptimizationsClient(client_wrapper=self._client_wrapper)
+ return self._optimizations
+
+
+class AsyncOptimizeClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawOptimizeClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._optimizations: typing.Optional[AsyncOptimizationsClient] = None
+
+ @property
+ def with_raw_response(self) -> AsyncRawOptimizeClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawOptimizeClient
+ """
+ return self._raw_client
+
+ async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> OptimizeReferenceListResponse:
+ """
+ Lists the `optimizeId` values the provided access token can access, and the resource each one comes from, such as a Webflow site or a non-Webflow site. Use an `optimizeId` with the other Optimize endpoints.
+
+ Results are sorted by `optimizeId` and aren't paginated.
+
+ Webflow sites accept OAuth app tokens and Site API tokens. Non-Webflow sites that use Optimize accept only OAuth app tokens, including tokens authorized for a whole workspace.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizeReferenceListResponse
+ The `optimizeId` values the access token can access.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.list()
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(request_options=request_options)
+ return _response.data
+
+ @property
+ def optimizations(self):
+ if self._optimizations is None:
+ from .resources.optimizations.client import AsyncOptimizationsClient # noqa: E402
+
+ self._optimizations = AsyncOptimizationsClient(client_wrapper=self._client_wrapper)
+ return self._optimizations
diff --git a/src/webflow/resources/optimize/raw_client.py b/src/webflow/resources/optimize/raw_client.py
new file mode 100644
index 00000000..e92c079b
--- /dev/null
+++ b/src/webflow/resources/optimize/raw_client.py
@@ -0,0 +1,241 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ...core.api_error import ApiError
+from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.http_response import AsyncHttpResponse, HttpResponse
+from ...core.parse_error import ParsingError
+from ...core.pydantic_utilities import parse_obj_as
+from ...core.request_options import RequestOptions
+from ...errors.forbidden_error import ForbiddenError
+from ...errors.internal_server_error import InternalServerError
+from ...errors.not_found_error import NotFoundError
+from ...errors.too_many_requests_error import TooManyRequestsError
+from ...errors.unauthorized_error import UnauthorizedError
+from ...types.error import Error
+from ...types.optimize_reference_list_response import OptimizeReferenceListResponse
+from pydantic import ValidationError
+
+
+class RawOptimizeClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[OptimizeReferenceListResponse]:
+ """
+ Lists the `optimizeId` values the provided access token can access, and the resource each one comes from, such as a Webflow site or a non-Webflow site. Use an `optimizeId` with the other Optimize endpoints.
+
+ Results are sorted by `optimizeId` and aren't paginated.
+
+ Webflow sites accept OAuth app tokens and Site API tokens. Non-Webflow sites that use Optimize accept only OAuth app tokens, including tokens authorized for a whole workspace.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizeReferenceListResponse]
+ The `optimizeId` values the access token can access.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "optimize",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizeReferenceListResponse,
+ parse_obj_as(
+ type_=OptimizeReferenceListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawOptimizeClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[OptimizeReferenceListResponse]:
+ """
+ Lists the `optimizeId` values the provided access token can access, and the resource each one comes from, such as a Webflow site or a non-Webflow site. Use an `optimizeId` with the other Optimize endpoints.
+
+ Results are sorted by `optimizeId` and aren't paginated.
+
+ Webflow sites accept OAuth app tokens and Site API tokens. Non-Webflow sites that use Optimize accept only OAuth app tokens, including tokens authorized for a whole workspace.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizeReferenceListResponse]
+ The `optimizeId` values the access token can access.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "optimize",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizeReferenceListResponse,
+ parse_obj_as(
+ type_=OptimizeReferenceListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/optimize/resources/__init__.py b/src/webflow/resources/optimize/resources/__init__.py
new file mode 100644
index 00000000..1e1975a7
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from . import optimizations
+_dynamic_imports: typing.Dict[str, str] = {"optimizations": ".optimizations"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["optimizations"]
diff --git a/src/webflow/resources/optimize/resources/optimizations/__init__.py b/src/webflow/resources/optimize/resources/optimizations/__init__.py
new file mode 100644
index 00000000..2ae461eb
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .resources import dimensions, goals
+_dynamic_imports: typing.Dict[str, str] = {"dimensions": ".resources", "goals": ".resources"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["dimensions", "goals"]
diff --git a/src/webflow/resources/optimize/resources/optimizations/client.py b/src/webflow/resources/optimize/resources/optimizations/client.py
new file mode 100644
index 00000000..0bde0164
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/client.py
@@ -0,0 +1,794 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import datetime as dt
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.optimization_audience_insights_response import OptimizationAudienceInsightsResponse
+from .....types.optimization_list_response import OptimizationListResponse
+from .....types.optimization_status import OptimizationStatus
+from .....types.optimization_summary_response import OptimizationSummaryResponse
+from .....types.optimization_trend_response import OptimizationTrendResponse
+from .....types.optimize_filter import OptimizeFilter
+from .....types.optimize_trend_granularity import OptimizeTrendGranularity
+from .raw_client import AsyncRawOptimizationsClient, RawOptimizationsClient
+
+if typing.TYPE_CHECKING:
+ from .resources.dimensions.client import AsyncDimensionsClient, DimensionsClient
+ from .resources.goals.client import AsyncGoalsClient, GoalsClient
+
+
+class OptimizationsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawOptimizationsClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._goals: typing.Optional[GoalsClient] = None
+ self._dimensions: typing.Optional[DimensionsClient] = None
+
+ @property
+ def with_raw_response(self) -> RawOptimizationsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawOptimizationsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ optimize_id: str,
+ *,
+ status: typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationListResponse:
+ """
+ Lists the optimizations for an `optimizeId`, with each one's type, status, intervals, and current goals. Returns live and draft optimizations unless you set `status`.
+
+ Each report covers a date range within a single interval of an optimization. Use an interval's `start` and `end` to choose a report's `intervalStart` and `intervalEnd`.
+
+ Each interval lists its goals as they were when it started. `currentGoals` lists the optimization's goals as they're set up now, and each goal's `source` shows where it comes from. A report accepts a `goalId` from either list.
+
+ Results are sorted by `optimizationId`, and each optimization's intervals are listed oldest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ status : typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]]
+ The statuses to include. Defaults to `live` and `draft`. Pass repeated values (`status=live&status=draft`) or a comma-separated list (`status=live,draft`).
+
+ A `cursor` keeps the statuses it was created with. When you pass a `cursor`, omit `status` or send the same statuses.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationListResponse
+ A page of optimizations.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.list(
+ optimize_id="117420042",
+ status=["live", "draft"],
+ page_size=1,
+ cursor="c2xhcnRpYmFydGZhc3Q",
+ )
+ """
+ _response = self._raw_client.list(
+ optimize_id, status=status, page_size=page_size, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ def summary(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationSummaryResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for one goal, over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Value goals also include each variation's total value and average conversion value. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationSummaryResponse
+ Each variation's results for the requested goal and date range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.summary(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ goal_id="198042421",
+ )
+ """
+ _response = self._raw_client.summary(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def audience_insights(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ group_by: str,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationAudienceInsightsResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for each segment of one dimension, for one goal over a date range within one interval. Choose the dimension with `groupBy`.
+
+ Each segment also shows how the variation is trending against the base variation in that segment, and how confident that trend is. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ group_by : str
+ The dimension to split results by, from [List Dimensions](/data/reference/optimize/list-dimensions). Accepts any dimension except `urlParameter`.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationAudienceInsightsResponse
+ Each variation's results for each segment of the `groupBy` dimension.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.audience_insights(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ group_by="country",
+ goal_id="198042421",
+ )
+ """
+ _response = self._raw_client.audience_insights(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ group_by=group_by,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def trend(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ granularity: OptimizeTrendGranularity,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationTrendResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate by day or week, for one goal over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Buckets use the account's time zone, returned as `timeZone`. For AI-optimized optimizations, `strength` covers the whole date range and is the same in every bucket. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ granularity : OptimizeTrendGranularity
+ Whether to bucket results by day or by week.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationTrendResponse
+ Each variation's results for each day or week in the requested date range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.trend(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ granularity="daily",
+ goal_id="198042421",
+ )
+ """
+ _response = self._raw_client.trend(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ granularity=granularity,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ @property
+ def goals(self):
+ if self._goals is None:
+ from .resources.goals.client import GoalsClient # noqa: E402
+
+ self._goals = GoalsClient(client_wrapper=self._client_wrapper)
+ return self._goals
+
+ @property
+ def dimensions(self):
+ if self._dimensions is None:
+ from .resources.dimensions.client import DimensionsClient # noqa: E402
+
+ self._dimensions = DimensionsClient(client_wrapper=self._client_wrapper)
+ return self._dimensions
+
+
+class AsyncOptimizationsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawOptimizationsClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._goals: typing.Optional[AsyncGoalsClient] = None
+ self._dimensions: typing.Optional[AsyncDimensionsClient] = None
+
+ @property
+ def with_raw_response(self) -> AsyncRawOptimizationsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawOptimizationsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ optimize_id: str,
+ *,
+ status: typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationListResponse:
+ """
+ Lists the optimizations for an `optimizeId`, with each one's type, status, intervals, and current goals. Returns live and draft optimizations unless you set `status`.
+
+ Each report covers a date range within a single interval of an optimization. Use an interval's `start` and `end` to choose a report's `intervalStart` and `intervalEnd`.
+
+ Each interval lists its goals as they were when it started. `currentGoals` lists the optimization's goals as they're set up now, and each goal's `source` shows where it comes from. A report accepts a `goalId` from either list.
+
+ Results are sorted by `optimizationId`, and each optimization's intervals are listed oldest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ status : typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]]
+ The statuses to include. Defaults to `live` and `draft`. Pass repeated values (`status=live&status=draft`) or a comma-separated list (`status=live,draft`).
+
+ A `cursor` keeps the statuses it was created with. When you pass a `cursor`, omit `status` or send the same statuses.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationListResponse
+ A page of optimizations.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.list(
+ optimize_id="117420042",
+ status=["live", "draft"],
+ page_size=1,
+ cursor="c2xhcnRpYmFydGZhc3Q",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(
+ optimize_id, status=status, page_size=page_size, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ async def summary(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationSummaryResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for one goal, over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Value goals also include each variation's total value and average conversion value. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationSummaryResponse
+ Each variation's results for the requested goal and date range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.summary(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ goal_id="198042421",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.summary(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def audience_insights(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ group_by: str,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationAudienceInsightsResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for each segment of one dimension, for one goal over a date range within one interval. Choose the dimension with `groupBy`.
+
+ Each segment also shows how the variation is trending against the base variation in that segment, and how confident that trend is. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ group_by : str
+ The dimension to split results by, from [List Dimensions](/data/reference/optimize/list-dimensions). Accepts any dimension except `urlParameter`.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationAudienceInsightsResponse
+ Each variation's results for each segment of the `groupBy` dimension.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.audience_insights(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ group_by="country",
+ goal_id="198042421",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.audience_insights(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ group_by=group_by,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def trend(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ granularity: OptimizeTrendGranularity,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationTrendResponse:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate by day or week, for one goal over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Buckets use the account's time zone, returned as `timeZone`. For AI-optimized optimizations, `strength` covers the whole date range and is the same in every bucket. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ granularity : OptimizeTrendGranularity
+ Whether to bucket results by day or by week.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationTrendResponse
+ Each variation's results for each day or week in the requested date range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.trend(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ granularity="daily",
+ goal_id="198042421",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.trend(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ granularity=granularity,
+ goal_id=goal_id,
+ filter=filter,
+ request_options=request_options,
+ )
+ return _response.data
+
+ @property
+ def goals(self):
+ if self._goals is None:
+ from .resources.goals.client import AsyncGoalsClient # noqa: E402
+
+ self._goals = AsyncGoalsClient(client_wrapper=self._client_wrapper)
+ return self._goals
+
+ @property
+ def dimensions(self):
+ if self._dimensions is None:
+ from .resources.dimensions.client import AsyncDimensionsClient # noqa: E402
+
+ self._dimensions = AsyncDimensionsClient(client_wrapper=self._client_wrapper)
+ return self._dimensions
diff --git a/src/webflow/resources/optimize/resources/optimizations/raw_client.py b/src/webflow/resources/optimize/resources/optimizations/raw_client.py
new file mode 100644
index 00000000..a1a35edd
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/raw_client.py
@@ -0,0 +1,1355 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.datetime_utils import serialize_datetime
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....core.serialization import convert_and_respect_annotation_metadata
+from .....errors.bad_request_error import BadRequestError
+from .....errors.conflict_error import ConflictError
+from .....errors.forbidden_error import ForbiddenError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.error import Error
+from .....types.optimization_audience_insights_response import OptimizationAudienceInsightsResponse
+from .....types.optimization_list_response import OptimizationListResponse
+from .....types.optimization_status import OptimizationStatus
+from .....types.optimization_summary_response import OptimizationSummaryResponse
+from .....types.optimization_trend_response import OptimizationTrendResponse
+from .....types.optimize_filter import OptimizeFilter
+from .....types.optimize_trend_granularity import OptimizeTrendGranularity
+from pydantic import ValidationError
+
+
+class RawOptimizationsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ optimize_id: str,
+ *,
+ status: typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationListResponse]:
+ """
+ Lists the optimizations for an `optimizeId`, with each one's type, status, intervals, and current goals. Returns live and draft optimizations unless you set `status`.
+
+ Each report covers a date range within a single interval of an optimization. Use an interval's `start` and `end` to choose a report's `intervalStart` and `intervalEnd`.
+
+ Each interval lists its goals as they were when it started. `currentGoals` lists the optimization's goals as they're set up now, and each goal's `source` shows where it comes from. A report accepts a `goalId` from either list.
+
+ Results are sorted by `optimizationId`, and each optimization's intervals are listed oldest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ status : typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]]
+ The statuses to include. Defaults to `live` and `draft`. Pass repeated values (`status=live&status=draft`) or a comma-separated list (`status=live,draft`).
+
+ A `cursor` keeps the statuses it was created with. When you pass a `cursor`, omit `status` or send the same statuses.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationListResponse]
+ A page of optimizations.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "status": status,
+ "pageSize": page_size,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationListResponse,
+ parse_obj_as(
+ type_=OptimizationListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def summary(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationSummaryResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for one goal, over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Value goals also include each variation's total value and average conversion value. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationSummaryResponse]
+ Each variation's results for the requested goal and date range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/summary",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationSummaryResponse,
+ parse_obj_as(
+ type_=OptimizationSummaryResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def audience_insights(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ group_by: str,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationAudienceInsightsResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for each segment of one dimension, for one goal over a date range within one interval. Choose the dimension with `groupBy`.
+
+ Each segment also shows how the variation is trending against the base variation in that segment, and how confident that trend is. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ group_by : str
+ The dimension to split results by, from [List Dimensions](/data/reference/optimize/list-dimensions). Accepts any dimension except `urlParameter`.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationAudienceInsightsResponse]
+ Each variation's results for each segment of the `groupBy` dimension.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/audience-insights",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "groupBy": group_by,
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationAudienceInsightsResponse,
+ parse_obj_as(
+ type_=OptimizationAudienceInsightsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def trend(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ granularity: OptimizeTrendGranularity,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationTrendResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate by day or week, for one goal over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Buckets use the account's time zone, returned as `timeZone`. For AI-optimized optimizations, `strength` covers the whole date range and is the same in every bucket. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ granularity : OptimizeTrendGranularity
+ Whether to bucket results by day or by week.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationTrendResponse]
+ Each variation's results for each day or week in the requested date range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/trend",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "granularity": granularity,
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationTrendResponse,
+ parse_obj_as(
+ type_=OptimizationTrendResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawOptimizationsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ optimize_id: str,
+ *,
+ status: typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationListResponse]:
+ """
+ Lists the optimizations for an `optimizeId`, with each one's type, status, intervals, and current goals. Returns live and draft optimizations unless you set `status`.
+
+ Each report covers a date range within a single interval of an optimization. Use an interval's `start` and `end` to choose a report's `intervalStart` and `intervalEnd`.
+
+ Each interval lists its goals as they were when it started. `currentGoals` lists the optimization's goals as they're set up now, and each goal's `source` shows where it comes from. A report accepts a `goalId` from either list.
+
+ Results are sorted by `optimizationId`, and each optimization's intervals are listed oldest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ status : typing.Optional[typing.Union[OptimizationStatus, typing.Sequence[OptimizationStatus]]]
+ The statuses to include. Defaults to `live` and `draft`. Pass repeated values (`status=live&status=draft`) or a comma-separated list (`status=live,draft`).
+
+ A `cursor` keeps the statuses it was created with. When you pass a `cursor`, omit `status` or send the same statuses.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationListResponse]
+ A page of optimizations.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "status": status,
+ "pageSize": page_size,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationListResponse,
+ parse_obj_as(
+ type_=OptimizationListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def summary(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationSummaryResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for one goal, over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Value goals also include each variation's total value and average conversion value. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationSummaryResponse]
+ Each variation's results for the requested goal and date range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/summary",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationSummaryResponse,
+ parse_obj_as(
+ type_=OptimizationSummaryResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def audience_insights(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ group_by: str,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationAudienceInsightsResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate for each segment of one dimension, for one goal over a date range within one interval. Choose the dimension with `groupBy`.
+
+ Each segment also shows how the variation is trending against the base variation in that segment, and how confident that trend is. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ group_by : str
+ The dimension to split results by, from [List Dimensions](/data/reference/optimize/list-dimensions). Accepts any dimension except `urlParameter`.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationAudienceInsightsResponse]
+ Each variation's results for each segment of the `groupBy` dimension.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/audience-insights",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "groupBy": group_by,
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationAudienceInsightsResponse,
+ parse_obj_as(
+ type_=OptimizationAudienceInsightsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def trend(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ granularity: OptimizeTrendGranularity,
+ goal_id: typing.Optional[str] = None,
+ filter: typing.Optional[OptimizeFilter] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationTrendResponse]:
+ """
+ Returns each variation's sessions, users, conversions, and conversion rate by day or week, for one goal over a date range within one interval. Traditional tests and AI-optimized optimizations also include each variation's comparison with the base variation.
+
+ Buckets use the account's time zone, returned as `timeZone`. For AI-optimized optimizations, `strength` covers the whole date range and is the same in every bucket. Choose the goal with `goalId`, and narrow the results with `filter`.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ granularity : OptimizeTrendGranularity
+ Whether to bucket results by day or by week.
+
+ goal_id : typing.Optional[str]
+ The goal to measure. Defaults to the interval's first target goal, or its first goal if it has no target goal. Use a `goalId` from the interval's `goals` or from the optimization's `currentGoals`. See [List Optimization Goals](/data/reference/optimize/list-optimization-goals).
+
+ If the goal is in both lists, the report uses the interval's version. A goal from `currentGoals` is measured with its current settings over the whole date range, including dates before it was added to the optimization.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationTrendResponse]
+ Each variation's results for each day or week in the requested date range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/trend",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "granularity": granularity,
+ "goalId": goal_id,
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationTrendResponse,
+ parse_obj_as(
+ type_=OptimizationTrendResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/__init__.py b/src/webflow/resources/optimize/resources/optimizations/resources/__init__.py
new file mode 100644
index 00000000..542b728e
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from . import dimensions, goals
+_dynamic_imports: typing.Dict[str, str] = {"dimensions": ".dimensions", "goals": ".goals"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["dimensions", "goals"]
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/__init__.py b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/__init__.py
new file mode 100644
index 00000000..5cde0202
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/client.py b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/client.py
new file mode 100644
index 00000000..e9463cdb
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/client.py
@@ -0,0 +1,387 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.request_options import RequestOptions
+from .......types.optimization_dimension_values_response import OptimizationDimensionValuesResponse
+from .......types.optimization_dimensions_response import OptimizationDimensionsResponse
+from .......types.optimize_filter import OptimizeFilter
+from .raw_client import AsyncRawDimensionsClient, RawDimensionsClient
+
+
+class DimensionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawDimensionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawDimensionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawDimensionsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationDimensionsResponse:
+ """
+ Lists the dimensions you can group or filter an optimization's reports by, for a date range within one interval. Use a dimension's `id` in `groupBy`, in `filter`, or to list its values.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationDimensionsResponse
+ The dimensions available for the requested date range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.dimensions.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ )
+ """
+ _response = self._raw_client.list(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def list_values(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ dimension_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ filter: typing.Optional[OptimizeFilter] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationDimensionValuesResponse:
+ """
+ Lists the values seen for one dimension over a date range within one interval, with how often each appeared. Use a `value` in `filter` to narrow a report.
+
+ Values are sorted by `eventCount`, highest first. To get the next page, pass `nextCursor` as `cursor`. Counts can change between requests, so a value can move to a different page while you page through the list.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ dimension_id : str
+ The dimension `id`, from [List Dimensions](/data/reference/optimize/list-dimensions). URL-encode it, because dimension IDs can contain `:`, `.`, and spaces. Unlike `groupBy`, this accepts `urlParameter`.
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page. Send the same dimension, `intervalStart`, `intervalEnd`, and `filter` as the original request. Note that `pageSize` can differ between requests.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationDimensionValuesResponse
+ A page of values seen for the dimension.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.dimensions.list_values(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ dimension_id="country",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ page_size=1,
+ cursor="ZmpvcmRz",
+ )
+ """
+ _response = self._raw_client.list_values(
+ optimize_id,
+ optimization_id,
+ dimension_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ filter=filter,
+ page_size=page_size,
+ cursor=cursor,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncDimensionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawDimensionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawDimensionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawDimensionsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationDimensionsResponse:
+ """
+ Lists the dimensions you can group or filter an optimization's reports by, for a date range within one interval. Use a dimension's `id` in `groupBy`, in `filter`, or to list its values.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationDimensionsResponse
+ The dimensions available for the requested date range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.dimensions.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def list_values(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ dimension_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ filter: typing.Optional[OptimizeFilter] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationDimensionValuesResponse:
+ """
+ Lists the values seen for one dimension over a date range within one interval, with how often each appeared. Use a `value` in `filter` to narrow a report.
+
+ Values are sorted by `eventCount`, highest first. To get the next page, pass `nextCursor` as `cursor`. Counts can change between requests, so a value can move to a different page while you page through the list.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ dimension_id : str
+ The dimension `id`, from [List Dimensions](/data/reference/optimize/list-dimensions). URL-encode it, because dimension IDs can contain `:`, `.`, and spaces. Unlike `groupBy`, this accepts `urlParameter`.
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page. Send the same dimension, `intervalStart`, `intervalEnd`, and `filter` as the original request. Note that `pageSize` can differ between requests.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationDimensionValuesResponse
+ A page of values seen for the dimension.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.dimensions.list_values(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ dimension_id="country",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ page_size=1,
+ cursor="ZmpvcmRz",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_values(
+ optimize_id,
+ optimization_id,
+ dimension_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ filter=filter,
+ page_size=page_size,
+ cursor=cursor,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/raw_client.py b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/raw_client.py
new file mode 100644
index 00000000..fef32927
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/dimensions/raw_client.py
@@ -0,0 +1,671 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .......core.api_error import ApiError
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.datetime_utils import serialize_datetime
+from .......core.http_response import AsyncHttpResponse, HttpResponse
+from .......core.jsonable_encoder import jsonable_encoder
+from .......core.parse_error import ParsingError
+from .......core.pydantic_utilities import parse_obj_as
+from .......core.request_options import RequestOptions
+from .......core.serialization import convert_and_respect_annotation_metadata
+from .......errors.bad_request_error import BadRequestError
+from .......errors.conflict_error import ConflictError
+from .......errors.forbidden_error import ForbiddenError
+from .......errors.internal_server_error import InternalServerError
+from .......errors.not_found_error import NotFoundError
+from .......errors.too_many_requests_error import TooManyRequestsError
+from .......errors.unauthorized_error import UnauthorizedError
+from .......types.error import Error
+from .......types.optimization_dimension_values_response import OptimizationDimensionValuesResponse
+from .......types.optimization_dimensions_response import OptimizationDimensionsResponse
+from .......types.optimize_filter import OptimizeFilter
+from pydantic import ValidationError
+
+
+class RawDimensionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationDimensionsResponse]:
+ """
+ Lists the dimensions you can group or filter an optimization's reports by, for a date range within one interval. Use a dimension's `id` in `groupBy`, in `filter`, or to list its values.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationDimensionsResponse]
+ The dimensions available for the requested date range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/dimensions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationDimensionsResponse,
+ parse_obj_as(
+ type_=OptimizationDimensionsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def list_values(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ dimension_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ filter: typing.Optional[OptimizeFilter] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationDimensionValuesResponse]:
+ """
+ Lists the values seen for one dimension over a date range within one interval, with how often each appeared. Use a `value` in `filter` to narrow a report.
+
+ Values are sorted by `eventCount`, highest first. To get the next page, pass `nextCursor` as `cursor`. Counts can change between requests, so a value can move to a different page while you page through the list.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ dimension_id : str
+ The dimension `id`, from [List Dimensions](/data/reference/optimize/list-dimensions). URL-encode it, because dimension IDs can contain `:`, `.`, and spaces. Unlike `groupBy`, this accepts `urlParameter`.
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page. Send the same dimension, `intervalStart`, `intervalEnd`, and `filter` as the original request. Note that `pageSize` can differ between requests.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationDimensionValuesResponse]
+ A page of values seen for the dimension.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/dimensions/{jsonable_encoder(dimension_id)}/values",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ "pageSize": page_size,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationDimensionValuesResponse,
+ parse_obj_as(
+ type_=OptimizationDimensionValuesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawDimensionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationDimensionsResponse]:
+ """
+ Lists the dimensions you can group or filter an optimization's reports by, for a date range within one interval. Use a dimension's `id` in `groupBy`, in `filter`, or to list its values.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationDimensionsResponse]
+ The dimensions available for the requested date range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/dimensions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationDimensionsResponse,
+ parse_obj_as(
+ type_=OptimizationDimensionsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def list_values(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ dimension_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ filter: typing.Optional[OptimizeFilter] = None,
+ page_size: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationDimensionValuesResponse]:
+ """
+ Lists the values seen for one dimension over a date range within one interval, with how often each appeared. Use a `value` in `filter` to narrow a report.
+
+ Values are sorted by `eventCount`, highest first. To get the next page, pass `nextCursor` as `cursor`. Counts can change between requests, so a value can move to a different page while you page through the list.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ dimension_id : str
+ The dimension `id`, from [List Dimensions](/data/reference/optimize/list-dimensions). URL-encode it, because dimension IDs can contain `:`, `.`, and spaces. Unlike `groupBy`, this accepts `urlParameter`.
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Can't be earlier than 18 months ago.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ filter : typing.Optional[OptimizeFilter]
+ Narrow the results to visitors who match dimension values. Use bracket notation with `in` or `nin` and indexed arrays, for example `filter[country][in][0]=US&filter[country][in][1]=GB&filter[customAttribute:towel][nin][0]=forgotten`.
+
+ Keys are dimension `id` values from [List Dimensions](/data/reference/optimize/list-dimensions), and values are raw `value` values from [List Dimension Values](/data/reference/optimize/list-dimension-values). Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.
+
+ page_size : typing.Optional[int]
+ The maximum number of results to return. Defaults to `100`, up to `200`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from a previous response, to get the next page. Send the same dimension, `intervalStart`, `intervalEnd`, and `filter` as the original request. Note that `pageSize` can differ between requests.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationDimensionValuesResponse]
+ A page of values seen for the dimension.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/dimensions/{jsonable_encoder(dimension_id)}/values",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ "filter": convert_and_respect_annotation_metadata(
+ object_=filter, annotation=OptimizeFilter, direction="write"
+ ),
+ "pageSize": page_size,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationDimensionValuesResponse,
+ parse_obj_as(
+ type_=OptimizationDimensionValuesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/goals/__init__.py b/src/webflow/resources/optimize/resources/optimizations/resources/goals/__init__.py
new file mode 100644
index 00000000..5cde0202
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/goals/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/goals/client.py b/src/webflow/resources/optimize/resources/optimizations/resources/goals/client.py
new file mode 100644
index 00000000..b4be450b
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/goals/client.py
@@ -0,0 +1,186 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.request_options import RequestOptions
+from .......types.optimization_goals_response import OptimizationGoalsResponse
+from .raw_client import AsyncRawGoalsClient, RawGoalsClient
+
+
+class GoalsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawGoalsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawGoalsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawGoalsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationGoalsResponse:
+ """
+ Lists an optimization's goals in two lists. `goals` holds the goals of the interval that contains the requested date range, as they were when it started, with its target goals marked. `currentGoals` holds the optimization's goals as they're set up now, with where each comes from. Use a `goalId` from either list to choose the goal a report measures.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Unlike the report endpoints, this endpoint has no 18-month limit.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationGoalsResponse
+ The optimization's goals for the interval that contains the requested date range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.optimize.optimizations.goals.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ )
+ """
+ _response = self._raw_client.list(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncGoalsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawGoalsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawGoalsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawGoalsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> OptimizationGoalsResponse:
+ """
+ Lists an optimization's goals in two lists. `goals` holds the goals of the interval that contains the requested date range, as they were when it started, with its target goals marked. `currentGoals` holds the optimization's goals as they're set up now, with where each comes from. Use a `goalId` from either list to choose the goal a report measures.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Unlike the report endpoints, this endpoint has no 18-month limit.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ OptimizationGoalsResponse
+ The optimization's goals for the interval that contains the requested date range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.optimize.optimizations.goals.list(
+ optimize_id="117420042",
+ optimization_id="417200971",
+ interval_start=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ interval_end=datetime.datetime.fromisoformat(
+ "2026-09-15 00:00:00+00:00",
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(
+ optimize_id,
+ optimization_id,
+ interval_start=interval_start,
+ interval_end=interval_end,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/optimize/resources/optimizations/resources/goals/raw_client.py b/src/webflow/resources/optimize/resources/optimizations/resources/goals/raw_client.py
new file mode 100644
index 00000000..34f13d4b
--- /dev/null
+++ b/src/webflow/resources/optimize/resources/optimizations/resources/goals/raw_client.py
@@ -0,0 +1,326 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .......core.api_error import ApiError
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.datetime_utils import serialize_datetime
+from .......core.http_response import AsyncHttpResponse, HttpResponse
+from .......core.jsonable_encoder import jsonable_encoder
+from .......core.parse_error import ParsingError
+from .......core.pydantic_utilities import parse_obj_as
+from .......core.request_options import RequestOptions
+from .......errors.bad_request_error import BadRequestError
+from .......errors.conflict_error import ConflictError
+from .......errors.forbidden_error import ForbiddenError
+from .......errors.internal_server_error import InternalServerError
+from .......errors.not_found_error import NotFoundError
+from .......errors.too_many_requests_error import TooManyRequestsError
+from .......errors.unauthorized_error import UnauthorizedError
+from .......types.error import Error
+from .......types.optimization_goals_response import OptimizationGoalsResponse
+from pydantic import ValidationError
+
+
+class RawGoalsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[OptimizationGoalsResponse]:
+ """
+ Lists an optimization's goals in two lists. `goals` holds the goals of the interval that contains the requested date range, as they were when it started, with its target goals marked. `currentGoals` holds the optimization's goals as they're set up now, with where each comes from. Use a `goalId` from either list to choose the goal a report measures.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Unlike the report endpoints, this endpoint has no 18-month limit.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[OptimizationGoalsResponse]
+ The optimization's goals for the interval that contains the requested date range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/goals",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationGoalsResponse,
+ parse_obj_as(
+ type_=OptimizationGoalsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawGoalsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ optimize_id: str,
+ optimization_id: str,
+ *,
+ interval_start: dt.datetime,
+ interval_end: dt.datetime,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[OptimizationGoalsResponse]:
+ """
+ Lists an optimization's goals in two lists. `goals` holds the goals of the interval that contains the requested date range, as they were when it started, with its target goals marked. `currentGoals` holds the optimization's goals as they're set up now, with where each comes from. Use a `goalId` from either list to choose the goal a report measures.
+
+ This endpoint requires a Webflow site with the Optimize add-on, or a non-Webflow site that uses Optimize.
+
+ Each access token can have one Optimize request in flight at a time, across all Optimize endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ optimize_id : str
+ The Optimize ID, from [List Optimize IDs](/data/reference/optimize/list-optimize-ids).
+
+ optimization_id : str
+ The optimization ID, from [List Optimizations](/data/reference/optimize/list-optimizations).
+
+ interval_start : dt.datetime
+ Start of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional. The date range must fall within a single interval of the optimization. A range that crosses from one interval into the next is rejected. Use an interval's `start` and `end` from [List Optimizations](/data/reference/optimize/list-optimizations). Unlike the report endpoints, this endpoint has no 18-month limit.
+
+ interval_end : dt.datetime
+ End of the date range. Must be a UTC timestamp ending in `Z`, such as `2026-09-15T00:00:00Z`, and later than `intervalStart`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[OptimizationGoalsResponse]
+ The optimization's goals for the interval that contains the requested date range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"optimize/{jsonable_encoder(optimize_id)}/optimizations/{jsonable_encoder(optimization_id)}/goals",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "intervalStart": serialize_datetime(interval_start),
+ "intervalEnd": serialize_datetime(interval_end),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ OptimizationGoalsResponse,
+ parse_obj_as(
+ type_=OptimizationGoalsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/pages/client.py b/src/webflow/resources/pages/client.py
index 9f6f9805..e9a09235 100644
--- a/src/webflow/resources/pages/client.py
+++ b/src/webflow/resources/pages/client.py
@@ -60,7 +60,7 @@ def list(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -100,6 +100,7 @@ def get_metadata(
page_id: str,
*,
locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Page:
"""
@@ -115,7 +116,20 @@ def get_metadata(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -135,9 +149,12 @@ def get_metadata(
client.pages.get_metadata(
page_id="63c720f9347c2139b248e552",
locale_id="65427cf400e02b306eaa04a0",
+ translatable="65427cf400e02b306eaa04a0",
)
"""
- _response = self._raw_client.get_metadata(page_id, locale_id=locale_id, request_options=request_options)
+ _response = self._raw_client.get_metadata(
+ page_id, locale_id=locale_id, translatable=translatable, request_options=request_options
+ )
return _response.data
def update_page_settings(
@@ -164,7 +181,7 @@ def update_page_settings(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
title : typing.Optional[str]
Title for the page
@@ -172,8 +189,9 @@ def update_page_settings(
slug : typing.Optional[str]
Slug for the page.
-
- **Note:** Updating slugs in secondary locales is only supported in Advanced and Enterprise localization add-on plans.
+ **Note:** The slug field is ignored in the following cases — all other fields in the same request still apply:
+ - The site's home page, collection template pages, and utility pages (e.g. 404, password, search).
+ - For secondary locales, updating the slug requires an Advanced or Enterprise localization add-on plan.
seo : typing.Optional[PageMetadataWriteSeo]
SEO-related fields for the Page
@@ -235,6 +253,7 @@ def get_content(
locale_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Dom:
"""
@@ -252,7 +271,7 @@ def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -260,6 +279,19 @@ def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -280,10 +312,16 @@ def get_content(
locale_id="65427cf400e02b306eaa04a0",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
"""
_response = self._raw_client.get_content(
- page_id, locale_id=locale_id, limit=limit, offset=offset, request_options=request_options
+ page_id,
+ locale_id=locale_id,
+ limit=limit,
+ offset=offset,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
@@ -447,7 +485,7 @@ async def list(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -495,6 +533,7 @@ async def get_metadata(
page_id: str,
*,
locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Page:
"""
@@ -510,7 +549,20 @@ async def get_metadata(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -535,12 +587,15 @@ async def main() -> None:
await client.pages.get_metadata(
page_id="63c720f9347c2139b248e552",
locale_id="65427cf400e02b306eaa04a0",
+ translatable="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
- _response = await self._raw_client.get_metadata(page_id, locale_id=locale_id, request_options=request_options)
+ _response = await self._raw_client.get_metadata(
+ page_id, locale_id=locale_id, translatable=translatable, request_options=request_options
+ )
return _response.data
async def update_page_settings(
@@ -567,7 +622,7 @@ async def update_page_settings(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
title : typing.Optional[str]
Title for the page
@@ -575,8 +630,9 @@ async def update_page_settings(
slug : typing.Optional[str]
Slug for the page.
-
- **Note:** Updating slugs in secondary locales is only supported in Advanced and Enterprise localization add-on plans.
+ **Note:** The slug field is ignored in the following cases — all other fields in the same request still apply:
+ - The site's home page, collection template pages, and utility pages (e.g. 404, password, search).
+ - For secondary locales, updating the slug requires an Advanced or Enterprise localization add-on plan.
seo : typing.Optional[PageMetadataWriteSeo]
SEO-related fields for the Page
@@ -646,6 +702,7 @@ async def get_content(
locale_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> Dom:
"""
@@ -663,7 +720,7 @@ async def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -671,6 +728,19 @@ async def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -696,13 +766,19 @@ async def main() -> None:
locale_id="65427cf400e02b306eaa04a0",
limit=1,
offset=1,
+ translatable="65427cf400e02b306eaa04a0",
)
asyncio.run(main())
"""
_response = await self._raw_client.get_content(
- page_id, locale_id=locale_id, limit=limit, offset=offset, request_options=request_options
+ page_id,
+ locale_id=locale_id,
+ limit=limit,
+ offset=offset,
+ translatable=translatable,
+ request_options=request_options,
)
return _response.data
diff --git a/src/webflow/resources/pages/raw_client.py b/src/webflow/resources/pages/raw_client.py
index 1287d929..1d6ee9ae 100644
--- a/src/webflow/resources/pages/raw_client.py
+++ b/src/webflow/resources/pages/raw_client.py
@@ -57,7 +57,7 @@ def list(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -163,6 +163,7 @@ def get_metadata(
page_id: str,
*,
locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[Page]:
"""
@@ -178,7 +179,20 @@ def get_metadata(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -194,6 +208,7 @@ def get_metadata(
method="GET",
params={
"localeId": locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -295,7 +310,7 @@ def update_page_settings(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
title : typing.Optional[str]
Title for the page
@@ -303,8 +318,9 @@ def update_page_settings(
slug : typing.Optional[str]
Slug for the page.
-
- **Note:** Updating slugs in secondary locales is only supported in Advanced and Enterprise localization add-on plans.
+ **Note:** The slug field is ignored in the following cases — all other fields in the same request still apply:
+ - The site's home page, collection template pages, and utility pages (e.g. 404, password, search).
+ - For secondary locales, updating the slug requires an Advanced or Enterprise localization add-on plan.
seo : typing.Optional[PageMetadataWriteSeo]
SEO-related fields for the Page
@@ -424,6 +440,7 @@ def get_content(
locale_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[Dom]:
"""
@@ -441,7 +458,7 @@ def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -449,6 +466,19 @@ def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -465,6 +495,7 @@ def get_content(
"localeId": locale_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -724,7 +755,7 @@ async def list(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -830,6 +861,7 @@ async def get_metadata(
page_id: str,
*,
locale_id: typing.Optional[str] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[Page]:
"""
@@ -845,7 +877,20 @@ async def get_metadata(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -861,6 +906,7 @@ async def get_metadata(
method="GET",
params={
"localeId": locale_id,
+ "translatable": translatable,
},
request_options=request_options,
)
@@ -962,7 +1008,7 @@ async def update_page_settings(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
title : typing.Optional[str]
Title for the page
@@ -970,8 +1016,9 @@ async def update_page_settings(
slug : typing.Optional[str]
Slug for the page.
-
- **Note:** Updating slugs in secondary locales is only supported in Advanced and Enterprise localization add-on plans.
+ **Note:** The slug field is ignored in the following cases — all other fields in the same request still apply:
+ - The site's home page, collection template pages, and utility pages (e.g. 404, password, search).
+ - For secondary locales, updating the slug requires an Advanced or Enterprise localization add-on plan.
seo : typing.Optional[PageMetadataWriteSeo]
SEO-related fields for the Page
@@ -1091,6 +1138,7 @@ async def get_content(
locale_id: typing.Optional[str] = None,
limit: typing.Optional[int] = None,
offset: typing.Optional[int] = None,
+ translatable: typing.Optional[str] = None,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[Dom]:
"""
@@ -1108,7 +1156,7 @@ async def get_content(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
limit : typing.Optional[int]
Maximum number of records to be returned (max limit: 100)
@@ -1116,6 +1164,19 @@ async def get_content(
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
+ translatable : typing.Optional[str]
+ Unique identifier for the secondary Locale you're translating **into**. Returns only content that hasn't been excluded from translation for that locale.
+
+ This is independent of `localeId`, which selects which version of the content is returned. To fetch the source text to translate, request the primary locale's content and set `translatable` to the locale you're translating into:
+
+ `?localeId={primary locale id}&translatable={target locale id}`
+
+ Only exclusion rules scoped to manual translation are respected — rules scoped only to automatic translation don't affect this parameter's response.
+
+ Omitting `translatable` returns the same response as if this parameter didn't exist. The value must be the id of one of the site's secondary locales — the primary locale id, or any other value, returns a `400` error. Requires translation exclusions to be enabled for the site; if they aren't, the request returns a `403` error.
+
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1132,6 +1193,7 @@ async def get_content(
"localeId": locale_id,
"limit": limit,
"offset": offset,
+ "translatable": translatable,
},
request_options=request_options,
)
diff --git a/src/webflow/resources/pages/resources/scripts/client.py b/src/webflow/resources/pages/resources/scripts/client.py
index 205dca52..0b11ef79 100644
--- a/src/webflow/resources/pages/resources/scripts/client.py
+++ b/src/webflow/resources/pages/resources/scripts/client.py
@@ -33,6 +33,8 @@ def get_custom_code(
"""
Get all scripts applied to a page.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -78,6 +80,8 @@ def upsert_custom_code(
To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -189,6 +193,8 @@ async def get_custom_code(
"""
Get all scripts applied to a page.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -242,6 +248,8 @@ async def upsert_custom_code(
To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
diff --git a/src/webflow/resources/pages/resources/scripts/raw_client.py b/src/webflow/resources/pages/resources/scripts/raw_client.py
index 6a71dd8b..9912b528 100644
--- a/src/webflow/resources/pages/resources/scripts/raw_client.py
+++ b/src/webflow/resources/pages/resources/scripts/raw_client.py
@@ -36,6 +36,8 @@ def get_custom_code(
"""
Get all scripts applied to a page.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -147,6 +149,8 @@ def upsert_custom_code(
To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -182,9 +186,6 @@ def upsert_custom_code(
"lastUpdated": last_updated,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -381,6 +382,8 @@ async def get_custom_code(
"""
Get all scripts applied to a page.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -492,6 +495,8 @@ async def upsert_custom_code(
To apply a script to a page, the script must first be registered to a Site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -527,9 +532,6 @@ async def upsert_custom_code(
"lastUpdated": last_updated,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/scripts/client.py b/src/webflow/resources/scripts/client.py
index f8a540e9..c072b05b 100644
--- a/src/webflow/resources/scripts/client.py
+++ b/src/webflow/resources/scripts/client.py
@@ -36,6 +36,8 @@ def list(self, site_id: str, *, request_options: typing.Optional[RequestOptions]
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -83,6 +85,8 @@ def register_hosted(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -157,6 +161,8 @@ def register_inline(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -238,6 +244,8 @@ async def list(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -293,6 +301,8 @@ async def register_hosted(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -375,6 +385,8 @@ async def register_inline(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
diff --git a/src/webflow/resources/scripts/raw_client.py b/src/webflow/resources/scripts/raw_client.py
index 0828f55c..757b54d9 100644
--- a/src/webflow/resources/scripts/raw_client.py
+++ b/src/webflow/resources/scripts/raw_client.py
@@ -39,6 +39,8 @@ def list(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -152,6 +154,8 @@ def register_hosted(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -291,6 +295,8 @@ def register_inline(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -427,6 +433,8 @@ async def list(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -540,6 +548,8 @@ async def register_hosted(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -679,6 +689,8 @@ async def register_inline(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
diff --git a/src/webflow/resources/sites/__init__.py b/src/webflow/resources/sites/__init__.py
index fb81115a..5354d021 100644
--- a/src/webflow/resources/sites/__init__.py
+++ b/src/webflow/resources/sites/__init__.py
@@ -6,7 +6,7 @@
from importlib import import_module
if typing.TYPE_CHECKING:
- from .types import SitesPublishResponse
+ from .types import SitesPublishResponse, SitesPublishResponsePublishScope
from .resources import (
CommentsGetCommentThreadRequestSortBy,
CommentsGetCommentThreadRequestSortOrder,
@@ -14,10 +14,14 @@
CommentsListCommentRepliesRequestSortOrder,
CommentsListCommentThreadsRequestSortBy,
CommentsListCommentThreadsRequestSortOrder,
+ GrantSiteMemberRequestBodySiteRole,
WellKnownFileContentType,
activity_logs,
+ aeo,
comments,
forms,
+ google_tag,
+ members,
plans,
redirects,
robots_txt,
@@ -31,11 +35,16 @@
"CommentsListCommentRepliesRequestSortOrder": ".resources",
"CommentsListCommentThreadsRequestSortBy": ".resources",
"CommentsListCommentThreadsRequestSortOrder": ".resources",
+ "GrantSiteMemberRequestBodySiteRole": ".resources",
"SitesPublishResponse": ".types",
+ "SitesPublishResponsePublishScope": ".types",
"WellKnownFileContentType": ".resources",
"activity_logs": ".resources",
+ "aeo": ".resources",
"comments": ".resources",
"forms": ".resources",
+ "google_tag": ".resources",
+ "members": ".resources",
"plans": ".resources",
"redirects": ".resources",
"robots_txt": ".resources",
@@ -72,11 +81,16 @@ def __dir__():
"CommentsListCommentRepliesRequestSortOrder",
"CommentsListCommentThreadsRequestSortBy",
"CommentsListCommentThreadsRequestSortOrder",
+ "GrantSiteMemberRequestBodySiteRole",
"SitesPublishResponse",
+ "SitesPublishResponsePublishScope",
"WellKnownFileContentType",
"activity_logs",
+ "aeo",
"comments",
"forms",
+ "google_tag",
+ "members",
"plans",
"redirects",
"robots_txt",
diff --git a/src/webflow/resources/sites/client.py b/src/webflow/resources/sites/client.py
index 30b9ca25..7346c69f 100644
--- a/src/webflow/resources/sites/client.py
+++ b/src/webflow/resources/sites/client.py
@@ -14,8 +14,11 @@
if typing.TYPE_CHECKING:
from .resources.activity_logs.client import ActivityLogsClient, AsyncActivityLogsClient
+ from .resources.aeo.client import AeoClient, AsyncAeoClient
from .resources.comments.client import AsyncCommentsClient, CommentsClient
from .resources.forms.client import AsyncFormsClient, FormsClient
+ from .resources.google_tag.client import AsyncGoogleTagClient, GoogleTagClient
+ from .resources.members.client import AsyncMembersClient, MembersClient
from .resources.plans.client import AsyncPlansClient, PlansClient
from .resources.redirects.client import AsyncRedirectsClient, RedirectsClient
from .resources.robots_txt.client import AsyncRobotsTxtClient, RobotsTxtClient
@@ -33,10 +36,13 @@ def __init__(self, *, client_wrapper: SyncClientWrapper):
self._plans: typing.Optional[PlansClient] = None
self._robots_txt: typing.Optional[RobotsTxtClient] = None
self._well_known: typing.Optional[WellKnownClient] = None
+ self._google_tag: typing.Optional[GoogleTagClient] = None
+ self._members: typing.Optional[MembersClient] = None
self._activity_logs: typing.Optional[ActivityLogsClient] = None
self._comments: typing.Optional[CommentsClient] = None
self._scripts: typing.Optional[ScriptsClient] = None
self._forms: typing.Optional[FormsClient] = None
+ self._aeo: typing.Optional[AeoClient] = None
@property
def with_raw_response(self) -> RawSitesClient:
@@ -68,7 +74,7 @@ def create(
Parameters
----------
workspace_id : str
- Unique identifier for a Workspace
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
name : str
The name of the site
@@ -292,13 +298,19 @@ def publish(
*,
custom_domains: typing.Optional[typing.Sequence[str]] = OMIT,
publish_to_webflow_subdomain: typing.Optional[bool] = OMIT,
+ page_id: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> SitesPublishResponse:
"""
- Publishes a site to one or more more domains.
+ Publishes a site or an individual page to one or more domains.
+ If multiple individual pages are published to staging, publishing from staging to production publishes all staged changes.
To publish to a specific custom domain, use the domain IDs from the [Get Custom Domains](/data/reference/sites/get-custom-domain) endpoint.
+ You must include at least one of the `customDomains` or `publishToWebflowSubdomain` properties in the request body.
+
+ To publish an individual page instead of the entire site, provide the ID of the page in the `pageId` parameter.
+
This endpoint has a specific rate limit of one successful publish queue per minute.
Required scope | `sites:write`
@@ -314,6 +326,9 @@ def publish(
publish_to_webflow_subdomain : typing.Optional[bool]
Choice of whether to publish to the default Webflow Subdomain
+ page_id : typing.Optional[str]
+ The ID of the page to publish
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -339,6 +354,7 @@ def publish(
site_id,
custom_domains=custom_domains,
publish_to_webflow_subdomain=publish_to_webflow_subdomain,
+ page_id=page_id,
request_options=request_options,
)
return _response.data
@@ -375,6 +391,22 @@ def well_known(self):
self._well_known = WellKnownClient(client_wrapper=self._client_wrapper)
return self._well_known
+ @property
+ def google_tag(self):
+ if self._google_tag is None:
+ from .resources.google_tag.client import GoogleTagClient # noqa: E402
+
+ self._google_tag = GoogleTagClient(client_wrapper=self._client_wrapper)
+ return self._google_tag
+
+ @property
+ def members(self):
+ if self._members is None:
+ from .resources.members.client import MembersClient # noqa: E402
+
+ self._members = MembersClient(client_wrapper=self._client_wrapper)
+ return self._members
+
@property
def activity_logs(self):
if self._activity_logs is None:
@@ -407,6 +439,14 @@ def forms(self):
self._forms = FormsClient(client_wrapper=self._client_wrapper)
return self._forms
+ @property
+ def aeo(self):
+ if self._aeo is None:
+ from .resources.aeo.client import AeoClient # noqa: E402
+
+ self._aeo = AeoClient(client_wrapper=self._client_wrapper)
+ return self._aeo
+
class AsyncSitesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -416,10 +456,13 @@ def __init__(self, *, client_wrapper: AsyncClientWrapper):
self._plans: typing.Optional[AsyncPlansClient] = None
self._robots_txt: typing.Optional[AsyncRobotsTxtClient] = None
self._well_known: typing.Optional[AsyncWellKnownClient] = None
+ self._google_tag: typing.Optional[AsyncGoogleTagClient] = None
+ self._members: typing.Optional[AsyncMembersClient] = None
self._activity_logs: typing.Optional[AsyncActivityLogsClient] = None
self._comments: typing.Optional[AsyncCommentsClient] = None
self._scripts: typing.Optional[AsyncScriptsClient] = None
self._forms: typing.Optional[AsyncFormsClient] = None
+ self._aeo: typing.Optional[AsyncAeoClient] = None
@property
def with_raw_response(self) -> AsyncRawSitesClient:
@@ -451,7 +494,7 @@ async def create(
Parameters
----------
workspace_id : str
- Unique identifier for a Workspace
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
name : str
The name of the site
@@ -725,13 +768,19 @@ async def publish(
*,
custom_domains: typing.Optional[typing.Sequence[str]] = OMIT,
publish_to_webflow_subdomain: typing.Optional[bool] = OMIT,
+ page_id: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> SitesPublishResponse:
"""
- Publishes a site to one or more more domains.
+ Publishes a site or an individual page to one or more domains.
+ If multiple individual pages are published to staging, publishing from staging to production publishes all staged changes.
To publish to a specific custom domain, use the domain IDs from the [Get Custom Domains](/data/reference/sites/get-custom-domain) endpoint.
+ You must include at least one of the `customDomains` or `publishToWebflowSubdomain` properties in the request body.
+
+ To publish an individual page instead of the entire site, provide the ID of the page in the `pageId` parameter.
+
This endpoint has a specific rate limit of one successful publish queue per minute.
Required scope | `sites:write`
@@ -747,6 +796,9 @@ async def publish(
publish_to_webflow_subdomain : typing.Optional[bool]
Choice of whether to publish to the default Webflow Subdomain
+ page_id : typing.Optional[str]
+ The ID of the page to publish
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -780,6 +832,7 @@ async def main() -> None:
site_id,
custom_domains=custom_domains,
publish_to_webflow_subdomain=publish_to_webflow_subdomain,
+ page_id=page_id,
request_options=request_options,
)
return _response.data
@@ -816,6 +869,22 @@ def well_known(self):
self._well_known = AsyncWellKnownClient(client_wrapper=self._client_wrapper)
return self._well_known
+ @property
+ def google_tag(self):
+ if self._google_tag is None:
+ from .resources.google_tag.client import AsyncGoogleTagClient # noqa: E402
+
+ self._google_tag = AsyncGoogleTagClient(client_wrapper=self._client_wrapper)
+ return self._google_tag
+
+ @property
+ def members(self):
+ if self._members is None:
+ from .resources.members.client import AsyncMembersClient # noqa: E402
+
+ self._members = AsyncMembersClient(client_wrapper=self._client_wrapper)
+ return self._members
+
@property
def activity_logs(self):
if self._activity_logs is None:
@@ -847,3 +916,11 @@ def forms(self):
self._forms = AsyncFormsClient(client_wrapper=self._client_wrapper)
return self._forms
+
+ @property
+ def aeo(self):
+ if self._aeo is None:
+ from .resources.aeo.client import AsyncAeoClient # noqa: E402
+
+ self._aeo = AsyncAeoClient(client_wrapper=self._client_wrapper)
+ return self._aeo
diff --git a/src/webflow/resources/sites/raw_client.py b/src/webflow/resources/sites/raw_client.py
index cc082502..b8176b56 100644
--- a/src/webflow/resources/sites/raw_client.py
+++ b/src/webflow/resources/sites/raw_client.py
@@ -50,7 +50,7 @@ def create(
Parameters
----------
workspace_id : str
- Unique identifier for a Workspace
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
name : str
The name of the site
@@ -686,13 +686,19 @@ def publish(
*,
custom_domains: typing.Optional[typing.Sequence[str]] = OMIT,
publish_to_webflow_subdomain: typing.Optional[bool] = OMIT,
+ page_id: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> HttpResponse[SitesPublishResponse]:
"""
- Publishes a site to one or more more domains.
+ Publishes a site or an individual page to one or more domains.
+ If multiple individual pages are published to staging, publishing from staging to production publishes all staged changes.
To publish to a specific custom domain, use the domain IDs from the [Get Custom Domains](/data/reference/sites/get-custom-domain) endpoint.
+ You must include at least one of the `customDomains` or `publishToWebflowSubdomain` properties in the request body.
+
+ To publish an individual page instead of the entire site, provide the ID of the page in the `pageId` parameter.
+
This endpoint has a specific rate limit of one successful publish queue per minute.
Required scope | `sites:write`
@@ -708,6 +714,9 @@ def publish(
publish_to_webflow_subdomain : typing.Optional[bool]
Choice of whether to publish to the default Webflow Subdomain
+ page_id : typing.Optional[str]
+ The ID of the page to publish
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -723,6 +732,7 @@ def publish(
json={
"customDomains": custom_domains,
"publishToWebflowSubdomain": publish_to_webflow_subdomain,
+ "pageId": page_id,
},
headers={
"content-type": "application/json",
@@ -828,7 +838,7 @@ async def create(
Parameters
----------
workspace_id : str
- Unique identifier for a Workspace
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
name : str
The name of the site
@@ -1468,13 +1478,19 @@ async def publish(
*,
custom_domains: typing.Optional[typing.Sequence[str]] = OMIT,
publish_to_webflow_subdomain: typing.Optional[bool] = OMIT,
+ page_id: typing.Optional[str] = OMIT,
request_options: typing.Optional[RequestOptions] = None,
) -> AsyncHttpResponse[SitesPublishResponse]:
"""
- Publishes a site to one or more more domains.
+ Publishes a site or an individual page to one or more domains.
+ If multiple individual pages are published to staging, publishing from staging to production publishes all staged changes.
To publish to a specific custom domain, use the domain IDs from the [Get Custom Domains](/data/reference/sites/get-custom-domain) endpoint.
+ You must include at least one of the `customDomains` or `publishToWebflowSubdomain` properties in the request body.
+
+ To publish an individual page instead of the entire site, provide the ID of the page in the `pageId` parameter.
+
This endpoint has a specific rate limit of one successful publish queue per minute.
Required scope | `sites:write`
@@ -1490,6 +1506,9 @@ async def publish(
publish_to_webflow_subdomain : typing.Optional[bool]
Choice of whether to publish to the default Webflow Subdomain
+ page_id : typing.Optional[str]
+ The ID of the page to publish
+
request_options : typing.Optional[RequestOptions]
Request-specific configuration.
@@ -1505,6 +1524,7 @@ async def publish(
json={
"customDomains": custom_domains,
"publishToWebflowSubdomain": publish_to_webflow_subdomain,
+ "pageId": page_id,
},
headers={
"content-type": "application/json",
diff --git a/src/webflow/resources/sites/resources/__init__.py b/src/webflow/resources/sites/resources/__init__.py
index 529b1de7..fe8ca534 100644
--- a/src/webflow/resources/sites/resources/__init__.py
+++ b/src/webflow/resources/sites/resources/__init__.py
@@ -6,7 +6,19 @@
from importlib import import_module
if typing.TYPE_CHECKING:
- from . import activity_logs, comments, forms, plans, redirects, robots_txt, scripts, well_known
+ from . import (
+ activity_logs,
+ aeo,
+ comments,
+ forms,
+ google_tag,
+ members,
+ plans,
+ redirects,
+ robots_txt,
+ scripts,
+ well_known,
+ )
from .comments import (
CommentsGetCommentThreadRequestSortBy,
CommentsGetCommentThreadRequestSortOrder,
@@ -15,6 +27,7 @@
CommentsListCommentThreadsRequestSortBy,
CommentsListCommentThreadsRequestSortOrder,
)
+ from .members import GrantSiteMemberRequestBodySiteRole
from .well_known import WellKnownFileContentType
_dynamic_imports: typing.Dict[str, str] = {
"CommentsGetCommentThreadRequestSortBy": ".comments",
@@ -23,10 +36,14 @@
"CommentsListCommentRepliesRequestSortOrder": ".comments",
"CommentsListCommentThreadsRequestSortBy": ".comments",
"CommentsListCommentThreadsRequestSortOrder": ".comments",
+ "GrantSiteMemberRequestBodySiteRole": ".members",
"WellKnownFileContentType": ".well_known",
"activity_logs": ".activity_logs",
+ "aeo": ".aeo",
"comments": ".comments",
"forms": ".forms",
+ "google_tag": ".google_tag",
+ "members": ".members",
"plans": ".plans",
"redirects": ".redirects",
"robots_txt": ".robots_txt",
@@ -63,10 +80,14 @@ def __dir__():
"CommentsListCommentRepliesRequestSortOrder",
"CommentsListCommentThreadsRequestSortBy",
"CommentsListCommentThreadsRequestSortOrder",
+ "GrantSiteMemberRequestBodySiteRole",
"WellKnownFileContentType",
"activity_logs",
+ "aeo",
"comments",
"forms",
+ "google_tag",
+ "members",
"plans",
"redirects",
"robots_txt",
diff --git a/src/webflow/resources/sites/resources/aeo/__init__.py b/src/webflow/resources/sites/resources/aeo/__init__.py
new file mode 100644
index 00000000..4ba11490
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/__init__.py
@@ -0,0 +1,55 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .resources import (
+ PromptsListRequestStatus,
+ ReportsCitationRateRequestBreakdown,
+ ReportsVisibilityBreakdownRequestBreakdown,
+ prompt_executions,
+ prompts,
+ reports,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "PromptsListRequestStatus": ".resources",
+ "ReportsCitationRateRequestBreakdown": ".resources",
+ "ReportsVisibilityBreakdownRequestBreakdown": ".resources",
+ "prompt_executions": ".resources",
+ "prompts": ".resources",
+ "reports": ".resources",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "PromptsListRequestStatus",
+ "ReportsCitationRateRequestBreakdown",
+ "ReportsVisibilityBreakdownRequestBreakdown",
+ "prompt_executions",
+ "prompts",
+ "reports",
+]
diff --git a/src/webflow/resources/sites/resources/aeo/client.py b/src/webflow/resources/sites/resources/aeo/client.py
new file mode 100644
index 00000000..2097e01c
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/client.py
@@ -0,0 +1,101 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .raw_client import AsyncRawAeoClient, RawAeoClient
+
+if typing.TYPE_CHECKING:
+ from .resources.prompt_executions.client import AsyncPromptExecutionsClient, PromptExecutionsClient
+ from .resources.prompts.client import AsyncPromptsClient, PromptsClient
+ from .resources.reports.client import AsyncReportsClient, ReportsClient
+
+
+class AeoClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawAeoClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._reports: typing.Optional[ReportsClient] = None
+ self._prompts: typing.Optional[PromptsClient] = None
+ self._prompt_executions: typing.Optional[PromptExecutionsClient] = None
+
+ @property
+ def with_raw_response(self) -> RawAeoClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawAeoClient
+ """
+ return self._raw_client
+
+ @property
+ def reports(self):
+ if self._reports is None:
+ from .resources.reports.client import ReportsClient # noqa: E402
+
+ self._reports = ReportsClient(client_wrapper=self._client_wrapper)
+ return self._reports
+
+ @property
+ def prompts(self):
+ if self._prompts is None:
+ from .resources.prompts.client import PromptsClient # noqa: E402
+
+ self._prompts = PromptsClient(client_wrapper=self._client_wrapper)
+ return self._prompts
+
+ @property
+ def prompt_executions(self):
+ if self._prompt_executions is None:
+ from .resources.prompt_executions.client import PromptExecutionsClient # noqa: E402
+
+ self._prompt_executions = PromptExecutionsClient(client_wrapper=self._client_wrapper)
+ return self._prompt_executions
+
+
+class AsyncAeoClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawAeoClient(client_wrapper=client_wrapper)
+ self._client_wrapper = client_wrapper
+ self._reports: typing.Optional[AsyncReportsClient] = None
+ self._prompts: typing.Optional[AsyncPromptsClient] = None
+ self._prompt_executions: typing.Optional[AsyncPromptExecutionsClient] = None
+
+ @property
+ def with_raw_response(self) -> AsyncRawAeoClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawAeoClient
+ """
+ return self._raw_client
+
+ @property
+ def reports(self):
+ if self._reports is None:
+ from .resources.reports.client import AsyncReportsClient # noqa: E402
+
+ self._reports = AsyncReportsClient(client_wrapper=self._client_wrapper)
+ return self._reports
+
+ @property
+ def prompts(self):
+ if self._prompts is None:
+ from .resources.prompts.client import AsyncPromptsClient # noqa: E402
+
+ self._prompts = AsyncPromptsClient(client_wrapper=self._client_wrapper)
+ return self._prompts
+
+ @property
+ def prompt_executions(self):
+ if self._prompt_executions is None:
+ from .resources.prompt_executions.client import AsyncPromptExecutionsClient # noqa: E402
+
+ self._prompt_executions = AsyncPromptExecutionsClient(client_wrapper=self._client_wrapper)
+ return self._prompt_executions
diff --git a/src/webflow/resources/sites/resources/aeo/raw_client.py b/src/webflow/resources/sites/resources/aeo/raw_client.py
new file mode 100644
index 00000000..2c191a4b
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/raw_client.py
@@ -0,0 +1,13 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+
+
+class RawAeoClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+
+class AsyncRawAeoClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
diff --git a/src/webflow/resources/sites/resources/aeo/resources/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/__init__.py
new file mode 100644
index 00000000..b45ced62
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/__init__.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from . import prompt_executions, prompts, reports
+ from .prompts import PromptsListRequestStatus
+ from .reports import ReportsCitationRateRequestBreakdown, ReportsVisibilityBreakdownRequestBreakdown
+_dynamic_imports: typing.Dict[str, str] = {
+ "PromptsListRequestStatus": ".prompts",
+ "ReportsCitationRateRequestBreakdown": ".reports",
+ "ReportsVisibilityBreakdownRequestBreakdown": ".reports",
+ "prompt_executions": ".prompt_executions",
+ "prompts": ".prompts",
+ "reports": ".reports",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "PromptsListRequestStatus",
+ "ReportsCitationRateRequestBreakdown",
+ "ReportsVisibilityBreakdownRequestBreakdown",
+ "prompt_executions",
+ "prompts",
+ "reports",
+]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/__init__.py
new file mode 100644
index 00000000..5cde0202
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/client.py b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/client.py
new file mode 100644
index 00000000..e6c4aa81
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/client.py
@@ -0,0 +1,350 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.request_options import RequestOptions
+from .......types.aeo_answer_engine_key import AeoAnswerEngineKey
+from .......types.aeo_prompt_execution import AeoPromptExecution
+from .......types.aeo_prompt_execution_list_response import AeoPromptExecutionListResponse
+from .raw_client import AsyncRawPromptExecutionsClient, RawPromptExecutionsClient
+
+
+class PromptExecutionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawPromptExecutionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawPromptExecutionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawPromptExecutionsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ prompt_id: typing.Optional[str] = None,
+ topic_id: typing.Optional[str] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ start_time: typing.Optional[dt.datetime] = None,
+ end_time: typing.Optional[dt.datetime] = None,
+ include_raw_response: typing.Optional[bool] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPromptExecutionListResponse:
+ """
+ Lists answer engine responses to the site's prompts, newest first, with the mentions and citations in each. Covers the last 90 days unless you set a time range.
+
+ Executions older than 90 days aren't listed. Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ prompt_id : typing.Optional[str]
+ Include only executions of this prompt. Can't be sent with `topicId`.
+
+ topic_id : typing.Optional[str]
+ Include only executions of prompts in this topic. Can't be sent with `promptId`.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ start_time : typing.Optional[dt.datetime]
+ Include only executions created at or after this time. Must be a UTC timestamp ending in `Z`. Defaults to 90 days before `endTime`.
+
+ end_time : typing.Optional[dt.datetime]
+ Include only executions created before this time. Must be a UTC timestamp ending in `Z`. Defaults to now. The range can be at most 90 days.
+
+ include_raw_response : typing.Optional[bool]
+ Set to `true` to include each execution's full response text in `rawResponse`.
+
+ limit : typing.Optional[int]
+ Maximum number of executions to return. Defaults to `25`, which is also the maximum.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptExecutionListResponse
+ A page of prompt executions.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.prompt_executions.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ prompt_id="68cdb4b5a699862fcd8c1a01",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ answer_engine="chat-gpt",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-10-01 00:00:00+00:00",
+ ),
+ include_raw_response=True,
+ limit=1,
+ cursor="cursor",
+ )
+ """
+ _response = self._raw_client.list(
+ site_id,
+ prompt_id=prompt_id,
+ topic_id=topic_id,
+ answer_engine=answer_engine,
+ start_time=start_time,
+ end_time=end_time,
+ include_raw_response=include_raw_response,
+ limit=limit,
+ cursor=cursor,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def get(
+ self, site_id: str, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AeoPromptExecution:
+ """
+ Returns one prompt execution, including the full response text while it's still stored.
+
+ Executions older than 90 days are returned without `rawResponse`. Executions are deleted 120 days after they're created. This endpoint takes no query parameters.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ execution_id : str
+ The prompt execution ID, from [List Prompt Executions](/data/reference/aeo-analytics/list-prompt-executions).
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptExecution
+ The prompt execution.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.prompt_executions.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ execution_id="68cdb4b5a699862fcd8c1a10",
+ )
+ """
+ _response = self._raw_client.get(site_id, execution_id, request_options=request_options)
+ return _response.data
+
+
+class AsyncPromptExecutionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawPromptExecutionsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawPromptExecutionsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawPromptExecutionsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ prompt_id: typing.Optional[str] = None,
+ topic_id: typing.Optional[str] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ start_time: typing.Optional[dt.datetime] = None,
+ end_time: typing.Optional[dt.datetime] = None,
+ include_raw_response: typing.Optional[bool] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPromptExecutionListResponse:
+ """
+ Lists answer engine responses to the site's prompts, newest first, with the mentions and citations in each. Covers the last 90 days unless you set a time range.
+
+ Executions older than 90 days aren't listed. Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ prompt_id : typing.Optional[str]
+ Include only executions of this prompt. Can't be sent with `topicId`.
+
+ topic_id : typing.Optional[str]
+ Include only executions of prompts in this topic. Can't be sent with `promptId`.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ start_time : typing.Optional[dt.datetime]
+ Include only executions created at or after this time. Must be a UTC timestamp ending in `Z`. Defaults to 90 days before `endTime`.
+
+ end_time : typing.Optional[dt.datetime]
+ Include only executions created before this time. Must be a UTC timestamp ending in `Z`. Defaults to now. The range can be at most 90 days.
+
+ include_raw_response : typing.Optional[bool]
+ Set to `true` to include each execution's full response text in `rawResponse`.
+
+ limit : typing.Optional[int]
+ Maximum number of executions to return. Defaults to `25`, which is also the maximum.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptExecutionListResponse
+ A page of prompt executions.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.prompt_executions.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ prompt_id="68cdb4b5a699862fcd8c1a01",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ answer_engine="chat-gpt",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-10-01 00:00:00+00:00",
+ ),
+ include_raw_response=True,
+ limit=1,
+ cursor="cursor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(
+ site_id,
+ prompt_id=prompt_id,
+ topic_id=topic_id,
+ answer_engine=answer_engine,
+ start_time=start_time,
+ end_time=end_time,
+ include_raw_response=include_raw_response,
+ limit=limit,
+ cursor=cursor,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def get(
+ self, site_id: str, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AeoPromptExecution:
+ """
+ Returns one prompt execution, including the full response text while it's still stored.
+
+ Executions older than 90 days are returned without `rawResponse`. Executions are deleted 120 days after they're created. This endpoint takes no query parameters.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ execution_id : str
+ The prompt execution ID, from [List Prompt Executions](/data/reference/aeo-analytics/list-prompt-executions).
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptExecution
+ The prompt execution.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.prompt_executions.get(
+ site_id="580e63e98c9a982ac9b8b741",
+ execution_id="68cdb4b5a699862fcd8c1a10",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(site_id, execution_id, request_options=request_options)
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/raw_client.py b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/raw_client.py
new file mode 100644
index 00000000..e22a6c1b
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompt_executions/raw_client.py
@@ -0,0 +1,603 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .......core.api_error import ApiError
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.datetime_utils import serialize_datetime
+from .......core.http_response import AsyncHttpResponse, HttpResponse
+from .......core.jsonable_encoder import jsonable_encoder
+from .......core.parse_error import ParsingError
+from .......core.pydantic_utilities import parse_obj_as
+from .......core.request_options import RequestOptions
+from .......errors.bad_request_error import BadRequestError
+from .......errors.forbidden_error import ForbiddenError
+from .......errors.internal_server_error import InternalServerError
+from .......errors.not_found_error import NotFoundError
+from .......errors.too_many_requests_error import TooManyRequestsError
+from .......errors.unauthorized_error import UnauthorizedError
+from .......types.aeo_answer_engine_key import AeoAnswerEngineKey
+from .......types.aeo_prompt_execution import AeoPromptExecution
+from .......types.aeo_prompt_execution_list_response import AeoPromptExecutionListResponse
+from .......types.error import Error
+from pydantic import ValidationError
+
+
+class RawPromptExecutionsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ prompt_id: typing.Optional[str] = None,
+ topic_id: typing.Optional[str] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ start_time: typing.Optional[dt.datetime] = None,
+ end_time: typing.Optional[dt.datetime] = None,
+ include_raw_response: typing.Optional[bool] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoPromptExecutionListResponse]:
+ """
+ Lists answer engine responses to the site's prompts, newest first, with the mentions and citations in each. Covers the last 90 days unless you set a time range.
+
+ Executions older than 90 days aren't listed. Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ prompt_id : typing.Optional[str]
+ Include only executions of this prompt. Can't be sent with `topicId`.
+
+ topic_id : typing.Optional[str]
+ Include only executions of prompts in this topic. Can't be sent with `promptId`.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ start_time : typing.Optional[dt.datetime]
+ Include only executions created at or after this time. Must be a UTC timestamp ending in `Z`. Defaults to 90 days before `endTime`.
+
+ end_time : typing.Optional[dt.datetime]
+ Include only executions created before this time. Must be a UTC timestamp ending in `Z`. Defaults to now. The range can be at most 90 days.
+
+ include_raw_response : typing.Optional[bool]
+ Set to `true` to include each execution's full response text in `rawResponse`.
+
+ limit : typing.Optional[int]
+ Maximum number of executions to return. Defaults to `25`, which is also the maximum.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoPromptExecutionListResponse]
+ A page of prompt executions.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompt_executions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "promptId": prompt_id,
+ "topicId": topic_id,
+ "answerEngine": answer_engine,
+ "startTime": serialize_datetime(start_time) if start_time is not None else None,
+ "endTime": serialize_datetime(end_time) if end_time is not None else None,
+ "includeRawResponse": include_raw_response,
+ "limit": limit,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptExecutionListResponse,
+ parse_obj_as(
+ type_=AeoPromptExecutionListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get(
+ self, site_id: str, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[AeoPromptExecution]:
+ """
+ Returns one prompt execution, including the full response text while it's still stored.
+
+ Executions older than 90 days are returned without `rawResponse`. Executions are deleted 120 days after they're created. This endpoint takes no query parameters.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ execution_id : str
+ The prompt execution ID, from [List Prompt Executions](/data/reference/aeo-analytics/list-prompt-executions).
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoPromptExecution]
+ The prompt execution.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompt_executions/{jsonable_encoder(execution_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptExecution,
+ parse_obj_as(
+ type_=AeoPromptExecution, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawPromptExecutionsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ prompt_id: typing.Optional[str] = None,
+ topic_id: typing.Optional[str] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ start_time: typing.Optional[dt.datetime] = None,
+ end_time: typing.Optional[dt.datetime] = None,
+ include_raw_response: typing.Optional[bool] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoPromptExecutionListResponse]:
+ """
+ Lists answer engine responses to the site's prompts, newest first, with the mentions and citations in each. Covers the last 90 days unless you set a time range.
+
+ Executions older than 90 days aren't listed. Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ prompt_id : typing.Optional[str]
+ Include only executions of this prompt. Can't be sent with `topicId`.
+
+ topic_id : typing.Optional[str]
+ Include only executions of prompts in this topic. Can't be sent with `promptId`.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ start_time : typing.Optional[dt.datetime]
+ Include only executions created at or after this time. Must be a UTC timestamp ending in `Z`. Defaults to 90 days before `endTime`.
+
+ end_time : typing.Optional[dt.datetime]
+ Include only executions created before this time. Must be a UTC timestamp ending in `Z`. Defaults to now. The range can be at most 90 days.
+
+ include_raw_response : typing.Optional[bool]
+ Set to `true` to include each execution's full response text in `rawResponse`.
+
+ limit : typing.Optional[int]
+ Maximum number of executions to return. Defaults to `25`, which is also the maximum.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoPromptExecutionListResponse]
+ A page of prompt executions.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompt_executions",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "promptId": prompt_id,
+ "topicId": topic_id,
+ "answerEngine": answer_engine,
+ "startTime": serialize_datetime(start_time) if start_time is not None else None,
+ "endTime": serialize_datetime(end_time) if end_time is not None else None,
+ "includeRawResponse": include_raw_response,
+ "limit": limit,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptExecutionListResponse,
+ parse_obj_as(
+ type_=AeoPromptExecutionListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get(
+ self, site_id: str, execution_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[AeoPromptExecution]:
+ """
+ Returns one prompt execution, including the full response text while it's still stored.
+
+ Executions older than 90 days are returned without `rawResponse`. Executions are deleted 120 days after they're created. This endpoint takes no query parameters.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ execution_id : str
+ The prompt execution ID, from [List Prompt Executions](/data/reference/aeo-analytics/list-prompt-executions).
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoPromptExecution]
+ The prompt execution.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompt_executions/{jsonable_encoder(execution_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptExecution,
+ parse_obj_as(
+ type_=AeoPromptExecution, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompts/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/prompts/__init__.py
new file mode 100644
index 00000000..47be6fa5
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompts/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import PromptsListRequestStatus
+_dynamic_imports: typing.Dict[str, str] = {"PromptsListRequestStatus": ".types"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["PromptsListRequestStatus"]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompts/client.py b/src/webflow/resources/sites/resources/aeo/resources/prompts/client.py
new file mode 100644
index 00000000..ceab3752
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompts/client.py
@@ -0,0 +1,181 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.request_options import RequestOptions
+from .......types.aeo_prompt_list_response import AeoPromptListResponse
+from .raw_client import AsyncRawPromptsClient, RawPromptsClient
+from .types.prompts_list_request_status import PromptsListRequestStatus
+
+
+class PromptsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawPromptsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawPromptsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawPromptsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ topic_id: typing.Optional[str] = None,
+ status: typing.Optional[PromptsListRequestStatus] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPromptListResponse:
+ """
+ Lists the site's tracked prompts, with each prompt's topic and status. By default, returns `active`, `paused`, and `pending` prompts. To include archived prompts, set `status` to `archived` or `all`.
+
+ Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ topic_id : typing.Optional[str]
+ Include only prompts in this topic. A topic that isn't on this site returns no prompts.
+
+ status : typing.Optional[PromptsListRequestStatus]
+ Include only prompts with this status. `all` includes every status. Omit it to include `active`, `paused`, and `pending` prompts.
+
+ limit : typing.Optional[int]
+ Maximum number of prompts to return. Defaults to `50`, up to a maximum of `100`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptListResponse
+ A page of prompts.
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.prompts.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ status="active",
+ limit=1,
+ cursor="cursor",
+ )
+ """
+ _response = self._raw_client.list(
+ site_id, topic_id=topic_id, status=status, limit=limit, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+
+class AsyncPromptsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawPromptsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawPromptsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawPromptsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ topic_id: typing.Optional[str] = None,
+ status: typing.Optional[PromptsListRequestStatus] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPromptListResponse:
+ """
+ Lists the site's tracked prompts, with each prompt's topic and status. By default, returns `active`, `paused`, and `pending` prompts. To include archived prompts, set `status` to `archived` or `all`.
+
+ Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ topic_id : typing.Optional[str]
+ Include only prompts in this topic. A topic that isn't on this site returns no prompts.
+
+ status : typing.Optional[PromptsListRequestStatus]
+ Include only prompts with this status. `all` includes every status. Omit it to include `active`, `paused`, and `pending` prompts.
+
+ limit : typing.Optional[int]
+ Maximum number of prompts to return. Defaults to `50`, up to a maximum of `100`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPromptListResponse
+ A page of prompts.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.prompts.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ topic_id="68cdb4b5a699862fcd8c1a02",
+ status="active",
+ limit=1,
+ cursor="cursor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(
+ site_id, topic_id=topic_id, status=status, limit=limit, cursor=cursor, request_options=request_options
+ )
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompts/raw_client.py b/src/webflow/resources/sites/resources/aeo/resources/prompts/raw_client.py
new file mode 100644
index 00000000..af4429aa
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompts/raw_client.py
@@ -0,0 +1,318 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from .......core.api_error import ApiError
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.http_response import AsyncHttpResponse, HttpResponse
+from .......core.jsonable_encoder import jsonable_encoder
+from .......core.parse_error import ParsingError
+from .......core.pydantic_utilities import parse_obj_as
+from .......core.request_options import RequestOptions
+from .......errors.bad_request_error import BadRequestError
+from .......errors.forbidden_error import ForbiddenError
+from .......errors.internal_server_error import InternalServerError
+from .......errors.not_found_error import NotFoundError
+from .......errors.too_many_requests_error import TooManyRequestsError
+from .......errors.unauthorized_error import UnauthorizedError
+from .......types.aeo_prompt_list_response import AeoPromptListResponse
+from .......types.error import Error
+from .types.prompts_list_request_status import PromptsListRequestStatus
+from pydantic import ValidationError
+
+
+class RawPromptsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ site_id: str,
+ *,
+ topic_id: typing.Optional[str] = None,
+ status: typing.Optional[PromptsListRequestStatus] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoPromptListResponse]:
+ """
+ Lists the site's tracked prompts, with each prompt's topic and status. By default, returns `active`, `paused`, and `pending` prompts. To include archived prompts, set `status` to `archived` or `all`.
+
+ Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ topic_id : typing.Optional[str]
+ Include only prompts in this topic. A topic that isn't on this site returns no prompts.
+
+ status : typing.Optional[PromptsListRequestStatus]
+ Include only prompts with this status. `all` includes every status. Omit it to include `active`, `paused`, and `pending` prompts.
+
+ limit : typing.Optional[int]
+ Maximum number of prompts to return. Defaults to `50`, up to a maximum of `100`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoPromptListResponse]
+ A page of prompts.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "topicId": topic_id,
+ "status": status,
+ "limit": limit,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptListResponse,
+ parse_obj_as(
+ type_=AeoPromptListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawPromptsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ site_id: str,
+ *,
+ topic_id: typing.Optional[str] = None,
+ status: typing.Optional[PromptsListRequestStatus] = None,
+ limit: typing.Optional[int] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoPromptListResponse]:
+ """
+ Lists the site's tracked prompts, with each prompt's topic and status. By default, returns `active`, `paused`, and `pending` prompts. To include archived prompts, set `status` to `archived` or `all`.
+
+ Results are sorted by `createdAt`, newest first. To get the next page, pass `nextCursor` as `cursor`.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ topic_id : typing.Optional[str]
+ Include only prompts in this topic. A topic that isn't on this site returns no prompts.
+
+ status : typing.Optional[PromptsListRequestStatus]
+ Include only prompts with this status. `all` includes every status. Omit it to include `active`, `paused`, and `pending` prompts.
+
+ limit : typing.Optional[int]
+ Maximum number of prompts to return. Defaults to `50`, up to a maximum of `100`.
+
+ cursor : typing.Optional[str]
+ The `nextCursor` from the previous page.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoPromptListResponse]
+ A page of prompts.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/prompts",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "topicId": topic_id,
+ "status": status,
+ "limit": limit,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPromptListResponse,
+ parse_obj_as(
+ type_=AeoPromptListResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompts/types/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/prompts/types/__init__.py
new file mode 100644
index 00000000..dffabeb6
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompts/types/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .prompts_list_request_status import PromptsListRequestStatus
+_dynamic_imports: typing.Dict[str, str] = {"PromptsListRequestStatus": ".prompts_list_request_status"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["PromptsListRequestStatus"]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/prompts/types/prompts_list_request_status.py b/src/webflow/resources/sites/resources/aeo/resources/prompts/types/prompts_list_request_status.py
new file mode 100644
index 00000000..e19f4442
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/prompts/types/prompts_list_request_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+PromptsListRequestStatus = typing.Union[typing.Literal["active", "paused", "pending", "archived", "all"], typing.Any]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/reports/__init__.py
new file mode 100644
index 00000000..b0b51552
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/__init__.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import ReportsCitationRateRequestBreakdown, ReportsVisibilityBreakdownRequestBreakdown
+_dynamic_imports: typing.Dict[str, str] = {
+ "ReportsCitationRateRequestBreakdown": ".types",
+ "ReportsVisibilityBreakdownRequestBreakdown": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["ReportsCitationRateRequestBreakdown", "ReportsVisibilityBreakdownRequestBreakdown"]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/client.py b/src/webflow/resources/sites/resources/aeo/resources/reports/client.py
new file mode 100644
index 00000000..947e3475
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/client.py
@@ -0,0 +1,1866 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.request_options import RequestOptions
+from .......types.aeo_answer_engine_key import AeoAnswerEngineKey
+from .......types.aeo_bot_category import AeoBotCategory
+from .......types.aeo_bot_provider import AeoBotProvider
+from .......types.aeo_bot_traffic_pages_response import AeoBotTrafficPagesResponse
+from .......types.aeo_bot_traffic_response import AeoBotTrafficResponse
+from .......types.aeo_citation_rate_response import AeoCitationRateResponse
+from .......types.aeo_citations_response import AeoCitationsResponse
+from .......types.aeo_cited_domain_paths_response import AeoCitedDomainPathsResponse
+from .......types.aeo_competitor_citations_response import AeoCompetitorCitationsResponse
+from .......types.aeo_page_citation_share_response import AeoPageCitationShareResponse
+from .......types.aeo_timeseries import AeoTimeseries
+from .......types.aeo_top_cited_domains_response import AeoTopCitedDomainsResponse
+from .......types.aeo_visibility_breakdown_response import AeoVisibilityBreakdownResponse
+from .......types.aeo_visibility_response import AeoVisibilityResponse
+from .raw_client import AsyncRawReportsClient, RawReportsClient
+from .types.reports_citation_rate_request_breakdown import ReportsCitationRateRequestBreakdown
+from .types.reports_visibility_breakdown_request_breakdown import ReportsVisibilityBreakdownRequestBreakdown
+
+
+class ReportsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawReportsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawReportsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawReportsClient
+ """
+ return self._raw_client
+
+ def bot_traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoBotTrafficResponse:
+ """
+ Returns the total number of requests made to the site by the AI bots Webflow tracks, over a time range. This counts bot requests, not visitors or page views. For visitor traffic, use the [Analyze API](/data/docs/working-with-analyze). Filter by bot provider or bot category.
+
+ Counts successful requests (HTTP `200` or `304`). Bot providers combine with OR, and `botCategory` narrows them further.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoBotTrafficResponse
+ The total bot requests for the time range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.bot_traffic(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ bot_providers=["openai"],
+ bot_category="agent",
+ )
+ """
+ _response = self._raw_client.bot_traffic(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ bot_providers=bot_providers,
+ bot_category=bot_category,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def bot_traffic_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoBotTrafficPagesResponse:
+ """
+ Returns the pages and files that the AI bots Webflow tracks requested most over a time range, with each one's request count.
+
+ Results are sorted by `requestCount`, highest first, and aren't paginated. Each row is one combination of page, CMS item, and request path, so a page can appear more than once.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `100`, which is also the maximum.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoBotTrafficPagesResponse
+ The pages and files bots requested most.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.bot_traffic_pages(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ bot_providers=["openai"],
+ bot_category="agent",
+ limit=1,
+ )
+ """
+ _response = self._raw_client.bot_traffic_pages(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ bot_providers=bot_providers,
+ bot_category=bot_category,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def visibility(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ timeseries: typing.Optional[AeoTimeseries] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoVisibilityResponse:
+ """
+ Returns the share of answer engine responses that mention your organization, for a time range or as a daily or weekly series.
+
+ Without `timeseries`, returns one score for the whole time range. With `timeseries`, returns one score per day or week. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ timeseries : typing.Optional[AeoTimeseries]
+ Return one score per day or week. Send both fields in bracket notation, such as `timeseries[granularityPeriod]=day×eries[bucketTimeZone]=America/New_York`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoVisibilityResponse
+ The visibility score for the time range.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.visibility(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engine="chat-gpt",
+ )
+ """
+ _response = self._raw_client.visibility(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engine=answer_engine,
+ timeseries=timeseries,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def visibility_breakdown(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsVisibilityBreakdownRequestBreakdown,
+ limit: typing.Optional[int] = None,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoVisibilityBreakdownResponse:
+ """
+ Returns the visibility score for each topic or prompt over a time range, highest first.
+
+ Choose `breakdown=topic` or `breakdown=prompt`. A topic or prompt appears only if it has responses in the time range. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsVisibilityBreakdownRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return, up to `100`. Omit it to return every row.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoVisibilityBreakdownResponse
+ The visibility score for each topic or prompt.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.visibility_breakdown(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ breakdown="topic",
+ limit=1,
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engine="chat-gpt",
+ )
+ """
+ _response = self._raw_client.visibility_breakdown(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ breakdown=breakdown,
+ limit=limit,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engine=answer_engine,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def citation_rate(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsCitationRateRequestBreakdown,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitationRateResponse:
+ """
+ Returns, for each topic or prompt, the share of responses with citations that cited one of your domains.
+
+ Your domains are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`. The response lists them in `domains`. Results are sorted by `citationRate`, highest first, and aren't paginated. Separate filters combine with AND, and values within one filter combine with OR.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsCitationRateRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitationRateResponse
+ The citation rate for each topic or prompt.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.citation_rate(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ breakdown="topic",
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engines=["chat-gpt"],
+ )
+ """
+ _response = self._raw_client.citation_rate(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ breakdown=breakdown,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def top_cited_domains(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoTopCitedDomainsResponse:
+ """
+ Returns the domains answer engines cited most, with each domain's share of all citations. For your domains, each row also lists its cited paths in `paths`. For other domains, `distinctPathCount` gives the approximate number of distinct cited paths.
+
+ Results are sorted by citation count, highest first. If one of your domains isn't in the top `limit`, it's added after the ranked rows, so the response can have more than `limit` rows.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Number of top domains to return. Defaults to `5`, up to a maximum of `100`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoTopCitedDomainsResponse
+ The most cited domains.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.top_cited_domains(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ )
+ """
+ _response = self._raw_client.top_cited_domains(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def cited_domain_paths(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ domain: str,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitedDomainPathsResponse:
+ """
+ Returns the cited paths on one domain, with each path's share of all citations.
+
+ `domain` can be any domain, not only yours. Results are sorted by `citationRate`, highest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ domain : str
+ The domain, as a bare hostname such as `example.com`, with no scheme, path, or port. Matched exactly, so `www.example.com` is a different domain. Send internationalized domains in punycode.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitedDomainPathsResponse
+ The cited paths on the domain.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.cited_domain_paths(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ domain="example.org",
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ )
+ """
+ _response = self._raw_client.cited_domain_paths(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ domain=domain,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def page_citation_share(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ breakdown: typing.Optional[typing.Literal["prompt"]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPageCitationShareResponse:
+ """
+ Returns one page's share of all citations to your domains, optionally broken down by prompt.
+
+ For a CMS item, send `collectionId` and `itemSlug` along with `pageId`. With `breakdown=prompt`, returns each prompt's share of this page's citations, sorted by `citationShare`, highest first.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ page_id : str
+ The page ID. For a CMS item, the ID of its template page.
+
+ collection_id : typing.Optional[str]
+ The CMS collection ID, for a CMS item. Send it with `itemSlug`.
+
+ item_slug : typing.Optional[str]
+ The CMS item's slug, for a CMS item. Send it with `collectionId`.
+
+ breakdown : typing.Optional[typing.Literal["prompt"]]
+ Set to `prompt` to return one row per prompt that cited the page.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPageCitationShareResponse
+ The page's citation share.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.page_citation_share(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ page_id="68cdb4b5a699862fcd8c1a30",
+ collection_id="68cdb4b5a699862fcd8c1a40",
+ item_slug="dont-panic",
+ answer_engines=["chat-gpt"],
+ )
+ """
+ _response = self._raw_client.page_citation_share(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ page_id=page_id,
+ collection_id=collection_id,
+ item_slug=item_slug,
+ breakdown=breakdown,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def competitor_citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCompetitorCitationsResponse:
+ """
+ Lists the newest responses that cited a competitor's domain and none of your domains.
+
+ Competitor domains come from the competitors in the site's Organization info. Results are sorted by `timestamp`, newest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `10`, up to a maximum of `1000`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCompetitorCitationsResponse
+ The newest responses that cited a competitor.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.competitor_citations(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ )
+ """
+ _response = self._raw_client.competitor_citations(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ offset: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitationsResponse:
+ """
+ Lists answer engine responses over a time range, newest first, with every citation and mention in each.
+
+ Includes responses that cited nothing. To get the next page, set `offset` to the previous `offset` plus `limit`. Rows can shift between pages if new responses arrive in the time range.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `100`, up to a maximum of `1000`.
+
+ offset : typing.Optional[int]
+ Number of responses to skip. Defaults to `0`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitationsResponse
+ A page of responses with their citations.
+
+ Examples
+ --------
+ import datetime
+
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.aeo.reports.citations(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ offset=1,
+ )
+ """
+ _response = self._raw_client.citations(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ offset=offset,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncReportsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawReportsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawReportsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawReportsClient
+ """
+ return self._raw_client
+
+ async def bot_traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoBotTrafficResponse:
+ """
+ Returns the total number of requests made to the site by the AI bots Webflow tracks, over a time range. This counts bot requests, not visitors or page views. For visitor traffic, use the [Analyze API](/data/docs/working-with-analyze). Filter by bot provider or bot category.
+
+ Counts successful requests (HTTP `200` or `304`). Bot providers combine with OR, and `botCategory` narrows them further.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoBotTrafficResponse
+ The total bot requests for the time range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.bot_traffic(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ bot_providers=["openai"],
+ bot_category="agent",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.bot_traffic(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ bot_providers=bot_providers,
+ bot_category=bot_category,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def bot_traffic_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoBotTrafficPagesResponse:
+ """
+ Returns the pages and files that the AI bots Webflow tracks requested most over a time range, with each one's request count.
+
+ Results are sorted by `requestCount`, highest first, and aren't paginated. Each row is one combination of page, CMS item, and request path, so a page can appear more than once.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `100`, which is also the maximum.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoBotTrafficPagesResponse
+ The pages and files bots requested most.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.bot_traffic_pages(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ bot_providers=["openai"],
+ bot_category="agent",
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.bot_traffic_pages(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ bot_providers=bot_providers,
+ bot_category=bot_category,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def visibility(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ timeseries: typing.Optional[AeoTimeseries] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoVisibilityResponse:
+ """
+ Returns the share of answer engine responses that mention your organization, for a time range or as a daily or weekly series.
+
+ Without `timeseries`, returns one score for the whole time range. With `timeseries`, returns one score per day or week. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ timeseries : typing.Optional[AeoTimeseries]
+ Return one score per day or week. Send both fields in bracket notation, such as `timeseries[granularityPeriod]=day×eries[bucketTimeZone]=America/New_York`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoVisibilityResponse
+ The visibility score for the time range.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.visibility(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engine="chat-gpt",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.visibility(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engine=answer_engine,
+ timeseries=timeseries,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def visibility_breakdown(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsVisibilityBreakdownRequestBreakdown,
+ limit: typing.Optional[int] = None,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoVisibilityBreakdownResponse:
+ """
+ Returns the visibility score for each topic or prompt over a time range, highest first.
+
+ Choose `breakdown=topic` or `breakdown=prompt`. A topic or prompt appears only if it has responses in the time range. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsVisibilityBreakdownRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return, up to `100`. Omit it to return every row.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoVisibilityBreakdownResponse
+ The visibility score for each topic or prompt.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.visibility_breakdown(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ breakdown="topic",
+ limit=1,
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engine="chat-gpt",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.visibility_breakdown(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ breakdown=breakdown,
+ limit=limit,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engine=answer_engine,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def citation_rate(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsCitationRateRequestBreakdown,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitationRateResponse:
+ """
+ Returns, for each topic or prompt, the share of responses with citations that cited one of your domains.
+
+ Your domains are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`. The response lists them in `domains`. Results are sorted by `citationRate`, highest first, and aren't paginated. Separate filters combine with AND, and values within one filter combine with OR.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsCitationRateRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitationRateResponse
+ The citation rate for each topic or prompt.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.citation_rate(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ breakdown="topic",
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engines=["chat-gpt"],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.citation_rate(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ breakdown=breakdown,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def top_cited_domains(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoTopCitedDomainsResponse:
+ """
+ Returns the domains answer engines cited most, with each domain's share of all citations. For your domains, each row also lists its cited paths in `paths`. For other domains, `distinctPathCount` gives the approximate number of distinct cited paths.
+
+ Results are sorted by citation count, highest first. If one of your domains isn't in the top `limit`, it's added after the ranked rows, so the response can have more than `limit` rows.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Number of top domains to return. Defaults to `5`, up to a maximum of `100`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoTopCitedDomainsResponse
+ The most cited domains.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.top_cited_domains(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.top_cited_domains(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def cited_domain_paths(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ domain: str,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitedDomainPathsResponse:
+ """
+ Returns the cited paths on one domain, with each path's share of all citations.
+
+ `domain` can be any domain, not only yours. Results are sorted by `citationRate`, highest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ domain : str
+ The domain, as a bare hostname such as `example.com`, with no scheme, path, or port. Matched exactly, so `www.example.com` is a different domain. Send internationalized domains in punycode.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitedDomainPathsResponse
+ The cited paths on the domain.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.cited_domain_paths(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ domain="example.org",
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.cited_domain_paths(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ domain=domain,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def page_citation_share(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ breakdown: typing.Optional[typing.Literal["prompt"]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoPageCitationShareResponse:
+ """
+ Returns one page's share of all citations to your domains, optionally broken down by prompt.
+
+ For a CMS item, send `collectionId` and `itemSlug` along with `pageId`. With `breakdown=prompt`, returns each prompt's share of this page's citations, sorted by `citationShare`, highest first.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ page_id : str
+ The page ID. For a CMS item, the ID of its template page.
+
+ collection_id : typing.Optional[str]
+ The CMS collection ID, for a CMS item. Send it with `itemSlug`.
+
+ item_slug : typing.Optional[str]
+ The CMS item's slug, for a CMS item. Send it with `collectionId`.
+
+ breakdown : typing.Optional[typing.Literal["prompt"]]
+ Set to `prompt` to return one row per prompt that cited the page.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoPageCitationShareResponse
+ The page's citation share.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.page_citation_share(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ page_id="68cdb4b5a699862fcd8c1a30",
+ collection_id="68cdb4b5a699862fcd8c1a40",
+ item_slug="dont-panic",
+ answer_engines=["chat-gpt"],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.page_citation_share(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ page_id=page_id,
+ collection_id=collection_id,
+ item_slug=item_slug,
+ breakdown=breakdown,
+ answer_engines=answer_engines,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def competitor_citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCompetitorCitationsResponse:
+ """
+ Lists the newest responses that cited a competitor's domain and none of your domains.
+
+ Competitor domains come from the competitors in the site's Organization info. Results are sorted by `timestamp`, newest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `10`, up to a maximum of `1000`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCompetitorCitationsResponse
+ The newest responses that cited a competitor.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.competitor_citations(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.competitor_citations(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ offset: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AeoCitationsResponse:
+ """
+ Lists answer engine responses over a time range, newest first, with every citation and mention in each.
+
+ Includes responses that cited nothing. To get the next page, set `offset` to the previous `offset` plus `limit`. Rows can shift between pages if new responses arrive in the time range.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `100`, up to a maximum of `1000`.
+
+ offset : typing.Optional[int]
+ Number of responses to skip. Defaults to `0`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AeoCitationsResponse
+ A page of responses with their citations.
+
+ Examples
+ --------
+ import asyncio
+ import datetime
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.aeo.reports.citations(
+ site_id="580e63e98c9a982ac9b8b741",
+ start_time=datetime.datetime.fromisoformat(
+ "2026-09-01 00:00:00+00:00",
+ ),
+ end_time=datetime.datetime.fromisoformat(
+ "2026-09-08 00:00:00+00:00",
+ ),
+ topic_ids=["68cdb4b5a699862fcd8c1a02"],
+ prompt_ids=["68cdb4b5a699862fcd8c1a01"],
+ answer_engines=["chat-gpt"],
+ limit=1,
+ offset=1,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.citations(
+ site_id,
+ start_time=start_time,
+ end_time=end_time,
+ topic_ids=topic_ids,
+ prompt_ids=prompt_ids,
+ answer_engines=answer_engines,
+ limit=limit,
+ offset=offset,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/raw_client.py b/src/webflow/resources/sites/resources/aeo/resources/reports/raw_client.py
new file mode 100644
index 00000000..aa378761
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/raw_client.py
@@ -0,0 +1,3083 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+from json.decoder import JSONDecodeError
+
+from .......core.api_error import ApiError
+from .......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .......core.datetime_utils import serialize_datetime
+from .......core.http_response import AsyncHttpResponse, HttpResponse
+from .......core.jsonable_encoder import jsonable_encoder
+from .......core.parse_error import ParsingError
+from .......core.pydantic_utilities import parse_obj_as
+from .......core.request_options import RequestOptions
+from .......core.serialization import convert_and_respect_annotation_metadata
+from .......errors.bad_request_error import BadRequestError
+from .......errors.forbidden_error import ForbiddenError
+from .......errors.internal_server_error import InternalServerError
+from .......errors.not_found_error import NotFoundError
+from .......errors.too_many_requests_error import TooManyRequestsError
+from .......errors.unauthorized_error import UnauthorizedError
+from .......types.aeo_answer_engine_key import AeoAnswerEngineKey
+from .......types.aeo_bot_category import AeoBotCategory
+from .......types.aeo_bot_provider import AeoBotProvider
+from .......types.aeo_bot_traffic_pages_response import AeoBotTrafficPagesResponse
+from .......types.aeo_bot_traffic_response import AeoBotTrafficResponse
+from .......types.aeo_citation_rate_response import AeoCitationRateResponse
+from .......types.aeo_citations_response import AeoCitationsResponse
+from .......types.aeo_cited_domain_paths_response import AeoCitedDomainPathsResponse
+from .......types.aeo_competitor_citations_response import AeoCompetitorCitationsResponse
+from .......types.aeo_page_citation_share_response import AeoPageCitationShareResponse
+from .......types.aeo_timeseries import AeoTimeseries
+from .......types.aeo_top_cited_domains_response import AeoTopCitedDomainsResponse
+from .......types.aeo_visibility_breakdown_response import AeoVisibilityBreakdownResponse
+from .......types.aeo_visibility_response import AeoVisibilityResponse
+from .......types.error import Error
+from .types.reports_citation_rate_request_breakdown import ReportsCitationRateRequestBreakdown
+from .types.reports_visibility_breakdown_request_breakdown import ReportsVisibilityBreakdownRequestBreakdown
+from pydantic import ValidationError
+
+
+class RawReportsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def bot_traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoBotTrafficResponse]:
+ """
+ Returns the total number of requests made to the site by the AI bots Webflow tracks, over a time range. This counts bot requests, not visitors or page views. For visitor traffic, use the [Analyze API](/data/docs/working-with-analyze). Filter by bot provider or bot category.
+
+ Counts successful requests (HTTP `200` or `304`). Bot providers combine with OR, and `botCategory` narrows them further.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoBotTrafficResponse]
+ The total bot requests for the time range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/bot_traffic",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "botProviders": bot_providers,
+ "botCategory": bot_category,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoBotTrafficResponse,
+ parse_obj_as(
+ type_=AeoBotTrafficResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def bot_traffic_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoBotTrafficPagesResponse]:
+ """
+ Returns the pages and files that the AI bots Webflow tracks requested most over a time range, with each one's request count.
+
+ Results are sorted by `requestCount`, highest first, and aren't paginated. Each row is one combination of page, CMS item, and request path, so a page can appear more than once.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `100`, which is also the maximum.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoBotTrafficPagesResponse]
+ The pages and files bots requested most.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/bot_traffic_pages",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "botProviders": bot_providers,
+ "botCategory": bot_category,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoBotTrafficPagesResponse,
+ parse_obj_as(
+ type_=AeoBotTrafficPagesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def visibility(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ timeseries: typing.Optional[AeoTimeseries] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoVisibilityResponse]:
+ """
+ Returns the share of answer engine responses that mention your organization, for a time range or as a daily or weekly series.
+
+ Without `timeseries`, returns one score for the whole time range. With `timeseries`, returns one score per day or week. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ timeseries : typing.Optional[AeoTimeseries]
+ Return one score per day or week. Send both fields in bracket notation, such as `timeseries[granularityPeriod]=day×eries[bucketTimeZone]=America/New_York`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoVisibilityResponse]
+ The visibility score for the time range.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/visibility",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngine": answer_engine,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AeoTimeseries, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoVisibilityResponse,
+ parse_obj_as(
+ type_=AeoVisibilityResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def visibility_breakdown(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsVisibilityBreakdownRequestBreakdown,
+ limit: typing.Optional[int] = None,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoVisibilityBreakdownResponse]:
+ """
+ Returns the visibility score for each topic or prompt over a time range, highest first.
+
+ Choose `breakdown=topic` or `breakdown=prompt`. A topic or prompt appears only if it has responses in the time range. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsVisibilityBreakdownRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return, up to `100`. Omit it to return every row.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoVisibilityBreakdownResponse]
+ The visibility score for each topic or prompt.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/visibility_breakdown",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "breakdown": breakdown,
+ "limit": limit,
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngine": answer_engine,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoVisibilityBreakdownResponse,
+ parse_obj_as(
+ type_=AeoVisibilityBreakdownResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def citation_rate(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsCitationRateRequestBreakdown,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoCitationRateResponse]:
+ """
+ Returns, for each topic or prompt, the share of responses with citations that cited one of your domains.
+
+ Your domains are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`. The response lists them in `domains`. Results are sorted by `citationRate`, highest first, and aren't paginated. Separate filters combine with AND, and values within one filter combine with OR.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsCitationRateRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoCitationRateResponse]
+ The citation rate for each topic or prompt.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/citation_rate",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "breakdown": breakdown,
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitationRateResponse,
+ parse_obj_as(
+ type_=AeoCitationRateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def top_cited_domains(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoTopCitedDomainsResponse]:
+ """
+ Returns the domains answer engines cited most, with each domain's share of all citations. For your domains, each row also lists its cited paths in `paths`. For other domains, `distinctPathCount` gives the approximate number of distinct cited paths.
+
+ Results are sorted by citation count, highest first. If one of your domains isn't in the top `limit`, it's added after the ranked rows, so the response can have more than `limit` rows.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Number of top domains to return. Defaults to `5`, up to a maximum of `100`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoTopCitedDomainsResponse]
+ The most cited domains.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/top_cited_domains",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoTopCitedDomainsResponse,
+ parse_obj_as(
+ type_=AeoTopCitedDomainsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def cited_domain_paths(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ domain: str,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoCitedDomainPathsResponse]:
+ """
+ Returns the cited paths on one domain, with each path's share of all citations.
+
+ `domain` can be any domain, not only yours. Results are sorted by `citationRate`, highest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ domain : str
+ The domain, as a bare hostname such as `example.com`, with no scheme, path, or port. Matched exactly, so `www.example.com` is a different domain. Send internationalized domains in punycode.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoCitedDomainPathsResponse]
+ The cited paths on the domain.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/cited_domain_paths",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "domain": domain,
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitedDomainPathsResponse,
+ parse_obj_as(
+ type_=AeoCitedDomainPathsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def page_citation_share(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ breakdown: typing.Optional[typing.Literal["prompt"]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoPageCitationShareResponse]:
+ """
+ Returns one page's share of all citations to your domains, optionally broken down by prompt.
+
+ For a CMS item, send `collectionId` and `itemSlug` along with `pageId`. With `breakdown=prompt`, returns each prompt's share of this page's citations, sorted by `citationShare`, highest first.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ page_id : str
+ The page ID. For a CMS item, the ID of its template page.
+
+ collection_id : typing.Optional[str]
+ The CMS collection ID, for a CMS item. Send it with `itemSlug`.
+
+ item_slug : typing.Optional[str]
+ The CMS item's slug, for a CMS item. Send it with `collectionId`.
+
+ breakdown : typing.Optional[typing.Literal["prompt"]]
+ Set to `prompt` to return one row per prompt that cited the page.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoPageCitationShareResponse]
+ The page's citation share.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/page_citation_share",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "pageId": page_id,
+ "collectionId": collection_id,
+ "itemSlug": item_slug,
+ "breakdown": breakdown,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPageCitationShareResponse,
+ parse_obj_as(
+ type_=AeoPageCitationShareResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def competitor_citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoCompetitorCitationsResponse]:
+ """
+ Lists the newest responses that cited a competitor's domain and none of your domains.
+
+ Competitor domains come from the competitors in the site's Organization info. Results are sorted by `timestamp`, newest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `10`, up to a maximum of `1000`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoCompetitorCitationsResponse]
+ The newest responses that cited a competitor.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/competitor_citations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCompetitorCitationsResponse,
+ parse_obj_as(
+ type_=AeoCompetitorCitationsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ offset: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[AeoCitationsResponse]:
+ """
+ Lists answer engine responses over a time range, newest first, with every citation and mention in each.
+
+ Includes responses that cited nothing. To get the next page, set `offset` to the previous `offset` plus `limit`. Rows can shift between pages if new responses arrive in the time range.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `100`, up to a maximum of `1000`.
+
+ offset : typing.Optional[int]
+ Number of responses to skip. Defaults to `0`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[AeoCitationsResponse]
+ A page of responses with their citations.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/citations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ "offset": offset,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitationsResponse,
+ parse_obj_as(
+ type_=AeoCitationsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawReportsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def bot_traffic(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoBotTrafficResponse]:
+ """
+ Returns the total number of requests made to the site by the AI bots Webflow tracks, over a time range. This counts bot requests, not visitors or page views. For visitor traffic, use the [Analyze API](/data/docs/working-with-analyze). Filter by bot provider or bot category.
+
+ Counts successful requests (HTTP `200` or `304`). Bot providers combine with OR, and `botCategory` narrows them further.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoBotTrafficResponse]
+ The total bot requests for the time range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/bot_traffic",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "botProviders": bot_providers,
+ "botCategory": bot_category,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoBotTrafficResponse,
+ parse_obj_as(
+ type_=AeoBotTrafficResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def bot_traffic_pages(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ bot_providers: typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]] = None,
+ bot_category: typing.Optional[AeoBotCategory] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoBotTrafficPagesResponse]:
+ """
+ Returns the pages and files that the AI bots Webflow tracks requested most over a time range, with each one's request count.
+
+ Results are sorted by `requestCount`, highest first, and aren't paginated. Each row is one combination of page, CMS item, and request path, so a page can appear more than once.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days. Can't be later than the start of the current UTC day, because today's data isn't available yet.
+
+ bot_providers : typing.Optional[typing.Union[AeoBotProvider, typing.Sequence[AeoBotProvider]]]
+ Count only bots from these providers. Repeat the parameter for more than one, such as `botProviders=openai&botProviders=anthropic`. Comma-separated values aren't accepted.
+
+ bot_category : typing.Optional[AeoBotCategory]
+ Count only bots in this category.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return. Defaults to `100`, which is also the maximum.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoBotTrafficPagesResponse]
+ The pages and files bots requested most.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/bot_traffic_pages",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "botProviders": bot_providers,
+ "botCategory": bot_category,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoBotTrafficPagesResponse,
+ parse_obj_as(
+ type_=AeoBotTrafficPagesResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def visibility(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ timeseries: typing.Optional[AeoTimeseries] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoVisibilityResponse]:
+ """
+ Returns the share of answer engine responses that mention your organization, for a time range or as a daily or weekly series.
+
+ Without `timeseries`, returns one score for the whole time range. With `timeseries`, returns one score per day or week. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ timeseries : typing.Optional[AeoTimeseries]
+ Return one score per day or week. Send both fields in bracket notation, such as `timeseries[granularityPeriod]=day×eries[bucketTimeZone]=America/New_York`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoVisibilityResponse]
+ The visibility score for the time range.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/visibility",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngine": answer_engine,
+ "timeseries": convert_and_respect_annotation_metadata(
+ object_=timeseries, annotation=AeoTimeseries, direction="write"
+ ),
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoVisibilityResponse,
+ parse_obj_as(
+ type_=AeoVisibilityResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def visibility_breakdown(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsVisibilityBreakdownRequestBreakdown,
+ limit: typing.Optional[int] = None,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engine: typing.Optional[AeoAnswerEngineKey] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoVisibilityBreakdownResponse]:
+ """
+ Returns the visibility score for each topic or prompt over a time range, highest first.
+
+ Choose `breakdown=topic` or `breakdown=prompt`. A topic or prompt appears only if it has responses in the time range. Separate filters combine with AND, and values within one filter combine with OR. Requires an organization name in the site's Organization info.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsVisibilityBreakdownRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ limit : typing.Optional[int]
+ Maximum number of rows to return, up to `100`. Omit it to return every row.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engine : typing.Optional[AeoAnswerEngineKey]
+ Include only responses from this answer engine.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoVisibilityBreakdownResponse]
+ The visibility score for each topic or prompt.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/visibility_breakdown",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "breakdown": breakdown,
+ "limit": limit,
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngine": answer_engine,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoVisibilityBreakdownResponse,
+ parse_obj_as(
+ type_=AeoVisibilityBreakdownResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def citation_rate(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ breakdown: ReportsCitationRateRequestBreakdown,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoCitationRateResponse]:
+ """
+ Returns, for each topic or prompt, the share of responses with citations that cited one of your domains.
+
+ Your domains are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`. The response lists them in `domains`. Results are sorted by `citationRate`, highest first, and aren't paginated. Separate filters combine with AND, and values within one filter combine with OR.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ breakdown : ReportsCitationRateRequestBreakdown
+ Return one row per `topic` or per `prompt`.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoCitationRateResponse]
+ The citation rate for each topic or prompt.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/citation_rate",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "breakdown": breakdown,
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitationRateResponse,
+ parse_obj_as(
+ type_=AeoCitationRateResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def top_cited_domains(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoTopCitedDomainsResponse]:
+ """
+ Returns the domains answer engines cited most, with each domain's share of all citations. For your domains, each row also lists its cited paths in `paths`. For other domains, `distinctPathCount` gives the approximate number of distinct cited paths.
+
+ Results are sorted by citation count, highest first. If one of your domains isn't in the top `limit`, it's added after the ranked rows, so the response can have more than `limit` rows.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Number of top domains to return. Defaults to `5`, up to a maximum of `100`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoTopCitedDomainsResponse]
+ The most cited domains.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/top_cited_domains",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoTopCitedDomainsResponse,
+ parse_obj_as(
+ type_=AeoTopCitedDomainsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def cited_domain_paths(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ domain: str,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoCitedDomainPathsResponse]:
+ """
+ Returns the cited paths on one domain, with each path's share of all citations.
+
+ `domain` can be any domain, not only yours. Results are sorted by `citationRate`, highest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ domain : str
+ The domain, as a bare hostname such as `example.com`, with no scheme, path, or port. Matched exactly, so `www.example.com` is a different domain. Send internationalized domains in punycode.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoCitedDomainPathsResponse]
+ The cited paths on the domain.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/cited_domain_paths",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "domain": domain,
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitedDomainPathsResponse,
+ parse_obj_as(
+ type_=AeoCitedDomainPathsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def page_citation_share(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ page_id: str,
+ collection_id: typing.Optional[str] = None,
+ item_slug: typing.Optional[str] = None,
+ breakdown: typing.Optional[typing.Literal["prompt"]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoPageCitationShareResponse]:
+ """
+ Returns one page's share of all citations to your domains, optionally broken down by prompt.
+
+ For a CMS item, send `collectionId` and `itemSlug` along with `pageId`. With `breakdown=prompt`, returns each prompt's share of this page's citations, sorted by `citationShare`, highest first.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ page_id : str
+ The page ID. For a CMS item, the ID of its template page.
+
+ collection_id : typing.Optional[str]
+ The CMS collection ID, for a CMS item. Send it with `itemSlug`.
+
+ item_slug : typing.Optional[str]
+ The CMS item's slug, for a CMS item. Send it with `collectionId`.
+
+ breakdown : typing.Optional[typing.Literal["prompt"]]
+ Set to `prompt` to return one row per prompt that cited the page.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoPageCitationShareResponse]
+ The page's citation share.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/page_citation_share",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "pageId": page_id,
+ "collectionId": collection_id,
+ "itemSlug": item_slug,
+ "breakdown": breakdown,
+ "answerEngines": answer_engines,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoPageCitationShareResponse,
+ parse_obj_as(
+ type_=AeoPageCitationShareResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def competitor_citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoCompetitorCitationsResponse]:
+ """
+ Lists the newest responses that cited a competitor's domain and none of your domains.
+
+ Competitor domains come from the competitors in the site's Organization info. Results are sorted by `timestamp`, newest first, and aren't paginated.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `10`, up to a maximum of `1000`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoCompetitorCitationsResponse]
+ The newest responses that cited a competitor.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/competitor_citations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCompetitorCitationsResponse,
+ parse_obj_as(
+ type_=AeoCompetitorCitationsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def citations(
+ self,
+ site_id: str,
+ *,
+ start_time: dt.datetime,
+ end_time: dt.datetime,
+ topic_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ prompt_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
+ answer_engines: typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]] = None,
+ limit: typing.Optional[int] = None,
+ offset: typing.Optional[int] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[AeoCitationsResponse]:
+ """
+ Lists answer engine responses over a time range, newest first, with every citation and mention in each.
+
+ Includes responses that cited nothing. To get the next page, set `offset` to the previous `offset` plus `limit`. Rows can shift between pages if new responses arrive in the time range.
+
+ This endpoint requires a site or workspace with the AEO analytics entitlement, and Webflow AI must be enabled for the workspace.
+
+ Each access token can have one AEO request in flight at a time, across all AEO Analytics endpoints. Additional concurrent requests return `429 Too Many Requests`. Wait for your in-flight request to finish, or for the `Retry-After` interval, then retry.
+
+ Required scope | `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ start_time : dt.datetime
+ Inclusive start of the time range. Must be a UTC timestamp ending in `Z`, such as `2026-09-01T00:00:00Z`. Offsets and date-only values aren't accepted. Seconds and milliseconds are optional.
+
+ end_time : dt.datetime
+ Exclusive end of the time range. Must be a UTC timestamp ending in `Z`, and later than `startTime`. The range can be at most 100 days.
+
+ topic_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these topics. Get topic IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ prompt_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]]
+ Include only these prompts. Get prompt IDs from [List Prompts](/data/reference/aeo-analytics/list-prompts). Repeat the parameter for more than one, up to 50.
+
+ answer_engines : typing.Optional[typing.Union[AeoAnswerEngineKey, typing.Sequence[AeoAnswerEngineKey]]]
+ Include only responses from these answer engines. Repeat the parameter for more than one, such as `answerEngines=chat-gpt&answerEngines=claude`.
+
+ limit : typing.Optional[int]
+ Maximum number of responses to return. Defaults to `100`, up to a maximum of `1000`.
+
+ offset : typing.Optional[int]
+ Number of responses to skip. Defaults to `0`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[AeoCitationsResponse]
+ A page of responses with their citations.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/aeo/reports/citations",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "startTime": serialize_datetime(start_time),
+ "endTime": serialize_datetime(end_time),
+ "topicIds": topic_ids,
+ "promptIds": prompt_ids,
+ "answerEngines": answer_engines,
+ "limit": limit,
+ "offset": offset,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ AeoCitationsResponse,
+ parse_obj_as(
+ type_=AeoCitationsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/types/__init__.py b/src/webflow/resources/sites/resources/aeo/resources/reports/types/__init__.py
new file mode 100644
index 00000000..007ce0d2
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/types/__init__.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .reports_citation_rate_request_breakdown import ReportsCitationRateRequestBreakdown
+ from .reports_visibility_breakdown_request_breakdown import ReportsVisibilityBreakdownRequestBreakdown
+_dynamic_imports: typing.Dict[str, str] = {
+ "ReportsCitationRateRequestBreakdown": ".reports_citation_rate_request_breakdown",
+ "ReportsVisibilityBreakdownRequestBreakdown": ".reports_visibility_breakdown_request_breakdown",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["ReportsCitationRateRequestBreakdown", "ReportsVisibilityBreakdownRequestBreakdown"]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_citation_rate_request_breakdown.py b/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_citation_rate_request_breakdown.py
new file mode 100644
index 00000000..2c82d4e2
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_citation_rate_request_breakdown.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsCitationRateRequestBreakdown = typing.Union[typing.Literal["topic", "prompt"], typing.Any]
diff --git a/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_visibility_breakdown_request_breakdown.py b/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_visibility_breakdown_request_breakdown.py
new file mode 100644
index 00000000..d843fb4c
--- /dev/null
+++ b/src/webflow/resources/sites/resources/aeo/resources/reports/types/reports_visibility_breakdown_request_breakdown.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ReportsVisibilityBreakdownRequestBreakdown = typing.Union[typing.Literal["topic", "prompt"], typing.Any]
diff --git a/src/webflow/resources/sites/resources/comments/client.py b/src/webflow/resources/sites/resources/comments/client.py
index 8fbae1e1..124c3c74 100644
--- a/src/webflow/resources/sites/resources/comments/client.py
+++ b/src/webflow/resources/sites/resources/comments/client.py
@@ -4,6 +4,7 @@
from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from .....core.request_options import RequestOptions
+from .....types.comment_reply import CommentReply
from .....types.comment_reply_list import CommentReplyList
from .....types.comment_thread import CommentThread
from .....types.comment_thread_list import CommentThreadList
@@ -15,6 +16,9 @@
from .types.comments_list_comment_threads_request_sort_by import CommentsListCommentThreadsRequestSortBy
from .types.comments_list_comment_threads_request_sort_order import CommentsListCommentThreadsRequestSortOrder
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
class CommentsClient:
def __init__(self, *, client_wrapper: SyncClientWrapper):
@@ -59,7 +63,7 @@ def list_comment_threads(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -140,7 +144,7 @@ def get_comment_thread(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -191,6 +195,60 @@ def get_comment_thread(
)
return _response.data
+ def resolve_comment_thread(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ resolved: bool,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CommentThread:
+ """
+ Resolve or unresolve a comment thread.
+
+
+ This endpoint is rate limited to 60 requests per minute per site.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ resolved : bool
+ Set to `true` to resolve the thread, or `false` to unresolve it
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CommentThread
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.comments.resolve_comment_thread(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ resolved=True,
+ )
+ """
+ _response = self._raw_client.resolve_comment_thread(
+ site_id, comment_thread_id, resolved=resolved, request_options=request_options
+ )
+ return _response.data
+
def list_comment_replies(
self,
site_id: str,
@@ -223,7 +281,7 @@ def list_comment_replies(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -274,6 +332,63 @@ def list_comment_replies(
)
return _response.data
+ def create_comment_reply(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ content: str,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CommentReply:
+ """
+ Create a reply to an existing comment thread.
+
+ The reply author is always the user who authorized the OAuth token.
+ To @mention a user in the reply, include their user ID in double square brackets in the `content` field, as in `[[userId]]`.
+
+
+ The `comment_created` webhook fires automatically when a reply is created.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ content : str
+ The text content of the reply. To @mention a user, include their user ID in double square brackets, as in `[[userId]]`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CommentReply
+ Reply created successfully
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.comments.create_comment_reply(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ content="Thanks for the feedback [[6287ec36a841b25637c663df]]!",
+ )
+ """
+ _response = self._raw_client.create_comment_reply(
+ site_id, comment_thread_id, content=content, request_options=request_options
+ )
+ return _response.data
+
class AsyncCommentsClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -318,7 +433,7 @@ async def list_comment_threads(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -407,7 +522,7 @@ async def get_comment_thread(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -466,6 +581,68 @@ async def main() -> None:
)
return _response.data
+ async def resolve_comment_thread(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ resolved: bool,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CommentThread:
+ """
+ Resolve or unresolve a comment thread.
+
+
+ This endpoint is rate limited to 60 requests per minute per site.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ resolved : bool
+ Set to `true` to resolve the thread, or `false` to unresolve it
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CommentThread
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.comments.resolve_comment_thread(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ resolved=True,
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.resolve_comment_thread(
+ site_id, comment_thread_id, resolved=resolved, request_options=request_options
+ )
+ return _response.data
+
async def list_comment_replies(
self,
site_id: str,
@@ -498,7 +675,7 @@ async def list_comment_replies(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -556,3 +733,68 @@ async def main() -> None:
request_options=request_options,
)
return _response.data
+
+ async def create_comment_reply(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ content: str,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> CommentReply:
+ """
+ Create a reply to an existing comment thread.
+
+ The reply author is always the user who authorized the OAuth token.
+ To @mention a user in the reply, include their user ID in double square brackets in the `content` field, as in `[[userId]]`.
+
+
+ The `comment_created` webhook fires automatically when a reply is created.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ content : str
+ The text content of the reply. To @mention a user, include their user ID in double square brackets, as in `[[userId]]`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ CommentReply
+ Reply created successfully
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.comments.create_comment_reply(
+ site_id="580e63e98c9a982ac9b8b741",
+ comment_thread_id="580e63e98c9a982ac9b8b741",
+ content="Thanks for the feedback [[6287ec36a841b25637c663df]]!",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create_comment_reply(
+ site_id, comment_thread_id, content=content, request_options=request_options
+ )
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/comments/raw_client.py b/src/webflow/resources/sites/resources/comments/raw_client.py
index 9502f35c..697192eb 100644
--- a/src/webflow/resources/sites/resources/comments/raw_client.py
+++ b/src/webflow/resources/sites/resources/comments/raw_client.py
@@ -15,6 +15,7 @@
from .....errors.not_found_error import NotFoundError
from .....errors.too_many_requests_error import TooManyRequestsError
from .....errors.unauthorized_error import UnauthorizedError
+from .....types.comment_reply import CommentReply
from .....types.comment_reply_list import CommentReplyList
from .....types.comment_thread import CommentThread
from .....types.comment_thread_list import CommentThreadList
@@ -27,6 +28,9 @@
from .types.comments_list_comment_threads_request_sort_order import CommentsListCommentThreadsRequestSortOrder
from pydantic import ValidationError
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
class RawCommentsClient:
def __init__(self, *, client_wrapper: SyncClientWrapper):
@@ -60,7 +64,7 @@ def list_comment_threads(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -201,7 +205,7 @@ def get_comment_thread(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -310,6 +314,129 @@ def get_comment_thread(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ def resolve_comment_thread(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ resolved: bool,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CommentThread]:
+ """
+ Resolve or unresolve a comment thread.
+
+
+ This endpoint is rate limited to 60 requests per minute per site.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ resolved : bool
+ Set to `true` to resolve the thread, or `false` to unresolve it
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CommentThread]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/comments/{jsonable_encoder(comment_thread_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "resolved": resolved,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CommentThread,
+ parse_obj_as(
+ type_=CommentThread, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
def list_comment_replies(
self,
site_id: str,
@@ -342,7 +469,7 @@ def list_comment_replies(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -451,6 +578,132 @@ def list_comment_replies(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ def create_comment_reply(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ content: str,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[CommentReply]:
+ """
+ Create a reply to an existing comment thread.
+
+ The reply author is always the user who authorized the OAuth token.
+ To @mention a user in the reply, include their user ID in double square brackets in the `content` field, as in `[[userId]]`.
+
+
+ The `comment_created` webhook fires automatically when a reply is created.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ content : str
+ The text content of the reply. To @mention a user, include their user ID in double square brackets, as in `[[userId]]`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[CommentReply]
+ Reply created successfully
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/comments/{jsonable_encoder(comment_thread_id)}/replies",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "content": content,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CommentReply,
+ parse_obj_as(
+ type_=CommentReply, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
class AsyncRawCommentsClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
@@ -484,7 +737,7 @@ async def list_comment_threads(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -625,7 +878,7 @@ async def get_comment_thread(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -734,6 +987,129 @@ async def get_comment_thread(
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+ async def resolve_comment_thread(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ resolved: bool,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CommentThread]:
+ """
+ Resolve or unresolve a comment thread.
+
+
+ This endpoint is rate limited to 60 requests per minute per site.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ resolved : bool
+ Set to `true` to resolve the thread, or `false` to unresolve it
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CommentThread]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/comments/{jsonable_encoder(comment_thread_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "resolved": resolved,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CommentThread,
+ parse_obj_as(
+ type_=CommentThread, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
async def list_comment_replies(
self,
site_id: str,
@@ -766,7 +1142,7 @@ async def list_comment_replies(
locale_id : typing.Optional[str]
Unique identifier for a specific Locale.
- [Lear more about localization.](/data/v2.0.0/docs/working-with-localization)
+ [Learn more about localization.](/data/v2.0.0/docs/working-with-localization)
offset : typing.Optional[int]
Offset used for pagination if the results have more than limit records
@@ -874,3 +1250,129 @@ async def list_comment_replies(
status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
)
raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def create_comment_reply(
+ self,
+ site_id: str,
+ comment_thread_id: str,
+ *,
+ content: str,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[CommentReply]:
+ """
+ Create a reply to an existing comment thread.
+
+ The reply author is always the user who authorized the OAuth token.
+ To @mention a user in the reply, include their user ID in double square brackets in the `content` field, as in `[[userId]]`.
+
+
+ The `comment_created` webhook fires automatically when a reply is created.
+
+
+ Required scope | `comments:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ comment_thread_id : str
+ Unique identifier for a Comment Thread
+
+ content : str
+ The text content of the reply. To @mention a user, include their user ID in double square brackets, as in `[[userId]]`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[CommentReply]
+ Reply created successfully
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/comments/{jsonable_encoder(comment_thread_id)}/replies",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "content": content,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ CommentReply,
+ parse_obj_as(
+ type_=CommentReply, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/google_tag/__init__.py b/src/webflow/resources/sites/resources/google_tag/__init__.py
new file mode 100644
index 00000000..5cde0202
--- /dev/null
+++ b/src/webflow/resources/sites/resources/google_tag/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/webflow/resources/sites/resources/google_tag/client.py b/src/webflow/resources/sites/resources/google_tag/client.py
new file mode 100644
index 00000000..1c0bf6b2
--- /dev/null
+++ b/src/webflow/resources/sites/resources/google_tag/client.py
@@ -0,0 +1,388 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.google_tag_id import GoogleTagId
+from .....types.google_tag_ids import GoogleTagIds
+from .raw_client import AsyncRawGoogleTagClient, RawGoogleTagClient
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class GoogleTagClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawGoogleTagClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawGoogleTagClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawGoogleTagClient
+ """
+ return self._raw_client
+
+ def list(self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GoogleTagIds:
+ """
+ List all Google Tag IDs configured for a site, sorted by order.
+
+ Required scope: `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.google_tag.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+ """
+ _response = self._raw_client.list(site_id, request_options=request_options)
+ return _response.data
+
+ def delete_all(self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
+ """
+ Delete all Google Tag IDs from a site.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.google_tag.delete_all(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+ """
+ _response = self._raw_client.delete_all(site_id, request_options=request_options)
+ return _response.data
+
+ def upsert(
+ self,
+ site_id: str,
+ *,
+ google_tag_ids: typing.Sequence[GoogleTagId],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GoogleTagIds:
+ """
+ Add or update Google Tag IDs for a site. Existing tags not referenced in the request are preserved. A site may have a maximum of 25 tags total.
+
+ `order` is optional on input — it is auto-assigned for new tags and returned on all tags in the response.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ google_tag_ids : typing.Sequence[GoogleTagId]
+ List of Google Tags configured for a site, sorted by order.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ from webflow import GoogleTagId, Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.google_tag.upsert(
+ site_id="580e63e98c9a982ac9b8b741",
+ google_tag_ids=[
+ GoogleTagId(
+ order=0,
+ display_name="Main Analytics Tag",
+ tag_id="G-1234567890",
+ )
+ ],
+ )
+ """
+ _response = self._raw_client.upsert(site_id, google_tag_ids=google_tag_ids, request_options=request_options)
+ return _response.data
+
+ def delete(
+ self, site_id: str, tag_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> GoogleTagIds:
+ """
+ Delete a single Google Tag ID from a site. The `order` values of the remaining tags are renormalized after deletion.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ tag_id : str
+ The Google Tag ID (e.g. G-XXXXXXXXXX)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.google_tag.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ tag_id="G-XXXXXXXXXX",
+ )
+ """
+ _response = self._raw_client.delete(site_id, tag_id, request_options=request_options)
+ return _response.data
+
+
+class AsyncGoogleTagClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawGoogleTagClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawGoogleTagClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawGoogleTagClient
+ """
+ return self._raw_client
+
+ async def list(self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> GoogleTagIds:
+ """
+ List all Google Tag IDs configured for a site, sorted by order.
+
+ Required scope: `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.google_tag.list(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(site_id, request_options=request_options)
+ return _response.data
+
+ async def delete_all(self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> None:
+ """
+ Delete all Google Tag IDs from a site.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.google_tag.delete_all(
+ site_id="580e63e98c9a982ac9b8b741",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.delete_all(site_id, request_options=request_options)
+ return _response.data
+
+ async def upsert(
+ self,
+ site_id: str,
+ *,
+ google_tag_ids: typing.Sequence[GoogleTagId],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GoogleTagIds:
+ """
+ Add or update Google Tag IDs for a site. Existing tags not referenced in the request are preserved. A site may have a maximum of 25 tags total.
+
+ `order` is optional on input — it is auto-assigned for new tags and returned on all tags in the response.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ google_tag_ids : typing.Sequence[GoogleTagId]
+ List of Google Tags configured for a site, sorted by order.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow, GoogleTagId
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.google_tag.upsert(
+ site_id="580e63e98c9a982ac9b8b741",
+ google_tag_ids=[
+ GoogleTagId(
+ order=0,
+ display_name="Main Analytics Tag",
+ tag_id="G-1234567890",
+ )
+ ],
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.upsert(
+ site_id, google_tag_ids=google_tag_ids, request_options=request_options
+ )
+ return _response.data
+
+ async def delete(
+ self, site_id: str, tag_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> GoogleTagIds:
+ """
+ Delete a single Google Tag ID from a site. The `order` values of the remaining tags are renormalized after deletion.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ tag_id : str
+ The Google Tag ID (e.g. G-XXXXXXXXXX)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GoogleTagIds
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.google_tag.delete(
+ site_id="580e63e98c9a982ac9b8b741",
+ tag_id="G-XXXXXXXXXX",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.delete(site_id, tag_id, request_options=request_options)
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/google_tag/raw_client.py b/src/webflow/resources/sites/resources/google_tag/raw_client.py
new file mode 100644
index 00000000..691f7bb4
--- /dev/null
+++ b/src/webflow/resources/sites/resources/google_tag/raw_client.py
@@ -0,0 +1,863 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....core.serialization import convert_and_respect_annotation_metadata
+from .....errors.bad_request_error import BadRequestError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.error import Error
+from .....types.google_tag_id import GoogleTagId
+from .....types.google_tag_ids import GoogleTagIds
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawGoogleTagClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[GoogleTagIds]:
+ """
+ List all Google Tag IDs configured for a site, sorted by order.
+
+ Required scope: `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def delete_all(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Delete all Google Tag IDs from a site.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def upsert(
+ self,
+ site_id: str,
+ *,
+ google_tag_ids: typing.Sequence[GoogleTagId],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[GoogleTagIds]:
+ """
+ Add or update Google Tag IDs for a site. Existing tags not referenced in the request are preserved. A site may have a maximum of 25 tags total.
+
+ `order` is optional on input — it is auto-assigned for new tags and returned on all tags in the response.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ google_tag_ids : typing.Sequence[GoogleTagId]
+ List of Google Tags configured for a site, sorted by order.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "googleTagIds": convert_and_respect_annotation_metadata(
+ object_=google_tag_ids, annotation=typing.Sequence[GoogleTagId], direction="write"
+ ),
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def delete(
+ self, site_id: str, tag_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[GoogleTagIds]:
+ """
+ Delete a single Google Tag ID from a site. The `order` values of the remaining tags are renormalized after deletion.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ tag_id : str
+ The Google Tag ID (e.g. G-XXXXXXXXXX)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags/{jsonable_encoder(tag_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawGoogleTagClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[GoogleTagIds]:
+ """
+ List all Google Tag IDs configured for a site, sorted by order.
+
+ Required scope: `sites:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def delete_all(
+ self, site_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Delete all Google Tag IDs from a site.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def upsert(
+ self,
+ site_id: str,
+ *,
+ google_tag_ids: typing.Sequence[GoogleTagId],
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[GoogleTagIds]:
+ """
+ Add or update Google Tag IDs for a site. Existing tags not referenced in the request are preserved. A site may have a maximum of 25 tags total.
+
+ `order` is optional on input — it is auto-assigned for new tags and returned on all tags in the response.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ google_tag_ids : typing.Sequence[GoogleTagId]
+ List of Google Tags configured for a site, sorted by order.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "googleTagIds": convert_and_respect_annotation_metadata(
+ object_=google_tag_ids, annotation=typing.Sequence[GoogleTagId], direction="write"
+ ),
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def delete(
+ self, site_id: str, tag_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[GoogleTagIds]:
+ """
+ Delete a single Google Tag ID from a site. The `order` values of the remaining tags are renormalized after deletion.
+
+ Required scope: `sites:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ tag_id : str
+ The Google Tag ID (e.g. G-XXXXXXXXXX)
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GoogleTagIds]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/integrations/google_tags/{jsonable_encoder(tag_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GoogleTagIds,
+ parse_obj_as(
+ type_=GoogleTagIds, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/members/__init__.py b/src/webflow/resources/sites/resources/members/__init__.py
new file mode 100644
index 00000000..db87af84
--- /dev/null
+++ b/src/webflow/resources/sites/resources/members/__init__.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import GrantSiteMemberRequestBodySiteRole
+_dynamic_imports: typing.Dict[str, str] = {"GrantSiteMemberRequestBodySiteRole": ".types"}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["GrantSiteMemberRequestBodySiteRole"]
diff --git a/src/webflow/resources/sites/resources/members/client.py b/src/webflow/resources/sites/resources/members/client.py
new file mode 100644
index 00000000..e2752d7e
--- /dev/null
+++ b/src/webflow/resources/sites/resources/members/client.py
@@ -0,0 +1,758 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.site_member import SiteMember
+from .....types.site_members_response import SiteMembersResponse
+from .....types.update_site_member_role_request_body_site_role import UpdateSiteMemberRoleRequestBodySiteRole
+from .raw_client import AsyncRawMembersClient, RawMembersClient
+from .types.grant_site_member_request_body_site_role import GrantSiteMemberRequestBodySiteRole
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class MembersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawMembersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawMembersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawMembersClient
+ """
+ return self._raw_client
+
+ def list_members(
+ self,
+ site_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMembersResponse:
+ """
+ List the members of a site and the role each holds.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Site Member with this email address
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMembersResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.list_members(
+ site_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ email="email",
+ cursor="cursor",
+ )
+ """
+ _response = self._raw_client.list_members(
+ site_id, limit=limit, email=email, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ def add_member(
+ self,
+ site_id: str,
+ *,
+ site_role: GrantSiteMemberRequestBodySiteRole,
+ email: typing.Optional[str] = OMIT,
+ member_id: typing.Optional[str] = OMIT,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Grant a workspace member access to a site, named by `email` or `memberId`.
+
+ This creates access — it does not change an existing grant. A member who already has access to this site is refused with `member_already_has_access`; use `PATCH` to change their Site Role instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ site_role : GrantSiteMemberRequestBodySiteRole
+ The Site Role to grant
+
+ email : typing.Optional[str]
+ Email address of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ member_id : typing.Optional[str]
+ ID of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to grant. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.add_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="waylon.dalton@example.com",
+ site_role="designer",
+ )
+ """
+ _response = self._raw_client.add_member(
+ site_id,
+ site_role=site_role,
+ email=email,
+ member_id=member_id,
+ site_role_id=site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def remove_member_by_email(
+ self, site_id: str, *, email: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member's access to a site, identifying them by email address.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ Equivalent to [Revoke Site Access](/data/reference/enterprise/site-access/remove-member), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member to revoke access from
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.remove_member_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+ )
+ """
+ _response = self._raw_client.remove_member_by_email(site_id, email=email, request_options=request_options)
+ return _response.data
+
+ def update_member_role_by_email(
+ self,
+ site_id: str,
+ *,
+ email: str,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Update a site member's role, identifying them by email address.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ Equivalent to [Update Site Member Role](/data/reference/enterprise/site-access/update-member-role), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member whose role to change
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.update_member_role_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+ site_role="content_editor",
+ )
+ """
+ _response = self._raw_client.update_member_role_by_email(
+ site_id, email=email, site_role=site_role, site_role_id=site_role_id, request_options=request_options
+ )
+ return _response.data
+
+ def remove_member(
+ self, site_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member's access to a site.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ To revoke by email address instead of by ID, see [Revoke Site Access (by email)](/data/reference/enterprise/site-access/remove-member-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.remove_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ )
+ """
+ _response = self._raw_client.remove_member(site_id, member_id, request_options=request_options)
+ return _response.data
+
+ def update_member_role(
+ self,
+ site_id: str,
+ member_id: str,
+ *,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Update a site member's role.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ To update by email address instead of by ID, see [Update Site Member Role (by email)](/data/reference/enterprise/site-access/update-member-role-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.sites.members.update_member_role(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ site_role="content_editor",
+ )
+ """
+ _response = self._raw_client.update_member_role(
+ site_id, member_id, site_role=site_role, site_role_id=site_role_id, request_options=request_options
+ )
+ return _response.data
+
+
+class AsyncMembersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawMembersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawMembersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawMembersClient
+ """
+ return self._raw_client
+
+ async def list_members(
+ self,
+ site_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMembersResponse:
+ """
+ List the members of a site and the role each holds.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Site Member with this email address
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMembersResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.list_members(
+ site_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ email="email",
+ cursor="cursor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_members(
+ site_id, limit=limit, email=email, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ async def add_member(
+ self,
+ site_id: str,
+ *,
+ site_role: GrantSiteMemberRequestBodySiteRole,
+ email: typing.Optional[str] = OMIT,
+ member_id: typing.Optional[str] = OMIT,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Grant a workspace member access to a site, named by `email` or `memberId`.
+
+ This creates access — it does not change an existing grant. A member who already has access to this site is refused with `member_already_has_access`; use `PATCH` to change their Site Role instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ site_role : GrantSiteMemberRequestBodySiteRole
+ The Site Role to grant
+
+ email : typing.Optional[str]
+ Email address of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ member_id : typing.Optional[str]
+ ID of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to grant. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.add_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="waylon.dalton@example.com",
+ site_role="designer",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.add_member(
+ site_id,
+ site_role=site_role,
+ email=email,
+ member_id=member_id,
+ site_role_id=site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def remove_member_by_email(
+ self, site_id: str, *, email: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member's access to a site, identifying them by email address.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ Equivalent to [Revoke Site Access](/data/reference/enterprise/site-access/remove-member), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member to revoke access from
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.remove_member_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.remove_member_by_email(site_id, email=email, request_options=request_options)
+ return _response.data
+
+ async def update_member_role_by_email(
+ self,
+ site_id: str,
+ *,
+ email: str,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Update a site member's role, identifying them by email address.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ Equivalent to [Update Site Member Role](/data/reference/enterprise/site-access/update-member-role), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member whose role to change
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.update_member_role_by_email(
+ site_id="580e63e98c9a982ac9b8b741",
+ email="email",
+ site_role="content_editor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update_member_role_by_email(
+ site_id, email=email, site_role=site_role, site_role_id=site_role_id, request_options=request_options
+ )
+ return _response.data
+
+ async def remove_member(
+ self, site_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member's access to a site.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ To revoke by email address instead of by ID, see [Revoke Site Access (by email)](/data/reference/enterprise/site-access/remove-member-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.remove_member(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.remove_member(site_id, member_id, request_options=request_options)
+ return _response.data
+
+ async def update_member_role(
+ self,
+ site_id: str,
+ member_id: str,
+ *,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SiteMember:
+ """
+ Update a site member's role.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ To update by email address instead of by ID, see [Update Site Member Role (by email)](/data/reference/enterprise/site-access/update-member-role-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SiteMember
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.sites.members.update_member_role(
+ site_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ site_role="content_editor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update_member_role(
+ site_id, member_id, site_role=site_role, site_role_id=site_role_id, request_options=request_options
+ )
+ return _response.data
diff --git a/src/webflow/resources/sites/resources/members/raw_client.py b/src/webflow/resources/sites/resources/members/raw_client.py
new file mode 100644
index 00000000..43a19497
--- /dev/null
+++ b/src/webflow/resources/sites/resources/members/raw_client.py
@@ -0,0 +1,1728 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....errors.bad_request_error import BadRequestError
+from .....errors.conflict_error import ConflictError
+from .....errors.forbidden_error import ForbiddenError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.error import Error
+from .....types.site_member import SiteMember
+from .....types.site_members_response import SiteMembersResponse
+from .....types.update_site_member_role_request_body_site_role import UpdateSiteMemberRoleRequestBodySiteRole
+from .types.grant_site_member_request_body_site_role import GrantSiteMemberRequestBodySiteRole
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawMembersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list_members(
+ self,
+ site_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[SiteMembersResponse]:
+ """
+ List the members of a site and the role each holds.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Site Member with this email address
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[SiteMembersResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "limit": limit,
+ "email": email,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMembersResponse,
+ parse_obj_as(
+ type_=SiteMembersResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def add_member(
+ self,
+ site_id: str,
+ *,
+ site_role: GrantSiteMemberRequestBodySiteRole,
+ email: typing.Optional[str] = OMIT,
+ member_id: typing.Optional[str] = OMIT,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[SiteMember]:
+ """
+ Grant a workspace member access to a site, named by `email` or `memberId`.
+
+ This creates access — it does not change an existing grant. A member who already has access to this site is refused with `member_already_has_access`; use `PATCH` to change their Site Role instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ site_role : GrantSiteMemberRequestBodySiteRole
+ The Site Role to grant
+
+ email : typing.Optional[str]
+ Email address of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ member_id : typing.Optional[str]
+ ID of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to grant. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "email": email,
+ "memberId": member_id,
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def remove_member_by_email(
+ self, site_id: str, *, email: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Remove a member's access to a site, identifying them by email address.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ Equivalent to [Revoke Site Access](/data/reference/enterprise/site-access/remove-member), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member to revoke access from
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ params={
+ "email": email,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def update_member_role_by_email(
+ self,
+ site_id: str,
+ *,
+ email: str,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[SiteMember]:
+ """
+ Update a site member's role, identifying them by email address.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ Equivalent to [Update Site Member Role](/data/reference/enterprise/site-access/update-member-role), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member whose role to change
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ params={
+ "email": email,
+ },
+ json={
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def remove_member(
+ self, site_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Remove a member's access to a site.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ To revoke by email address instead of by ID, see [Revoke Site Access (by email)](/data/reference/enterprise/site-access/remove-member-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def update_member_role(
+ self,
+ site_id: str,
+ member_id: str,
+ *,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[SiteMember]:
+ """
+ Update a site member's role.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ To update by email address instead of by ID, see [Update Site Member Role (by email)](/data/reference/enterprise/site-access/update-member-role-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawMembersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list_members(
+ self,
+ site_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[SiteMembersResponse]:
+ """
+ List the members of a site and the role each holds.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:read`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Site Member with this email address
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[SiteMembersResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "limit": limit,
+ "email": email,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMembersResponse,
+ parse_obj_as(
+ type_=SiteMembersResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def add_member(
+ self,
+ site_id: str,
+ *,
+ site_role: GrantSiteMemberRequestBodySiteRole,
+ email: typing.Optional[str] = OMIT,
+ member_id: typing.Optional[str] = OMIT,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[SiteMember]:
+ """
+ Grant a workspace member access to a site, named by `email` or `memberId`.
+
+ This creates access — it does not change an existing grant. A member who already has access to this site is refused with `member_already_has_access`; use `PATCH` to change their Site Role instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ site_role : GrantSiteMemberRequestBodySiteRole
+ The Site Role to grant
+
+ email : typing.Optional[str]
+ Email address of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ member_id : typing.Optional[str]
+ ID of the Workspace Member to grant access to. Provide either `email` or `memberId`, not both.
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to grant. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "email": email,
+ "memberId": member_id,
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def remove_member_by_email(
+ self, site_id: str, *, email: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Remove a member's access to a site, identifying them by email address.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ Equivalent to [Revoke Site Access](/data/reference/enterprise/site-access/remove-member), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member to revoke access from
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ params={
+ "email": email,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def update_member_role_by_email(
+ self,
+ site_id: str,
+ *,
+ email: str,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[SiteMember]:
+ """
+ Update a site member's role, identifying them by email address.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ Equivalent to [Update Site Member Role](/data/reference/enterprise/site-access/update-member-role), which identifies the member by ID instead.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ email : str
+ Email address of the Site Member whose role to change
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ params={
+ "email": email,
+ },
+ json={
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def remove_member(
+ self, site_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Remove a member's access to a site.
+
+ Idempotent on a member who already holds no access to this site — nothing is revoked and this reports success. Not idempotent on someone who isn't a workspace member at all, which returns a `404`.
+
+ To revoke by email address instead of by ID, see [Revoke Site Access (by email)](/data/reference/enterprise/site-access/remove-member-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def update_member_role(
+ self,
+ site_id: str,
+ member_id: str,
+ *,
+ site_role: UpdateSiteMemberRoleRequestBodySiteRole,
+ site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[SiteMember]:
+ """
+ Update a site member's role.
+
+ On a site with Site-specific access enabled, the member must already hold access to this site — grant it first with `POST`. On a site open to the whole workspace, this call sets a per-site role for the first time.
+
+ To update by email address instead of by ID, see [Update Site Member Role (by email)](/data/reference/enterprise/site-access/update-member-role-by-email).
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `site_access:write`
+
+ Parameters
+ ----------
+ site_id : str
+ Unique identifier for a Site
+
+ member_id : str
+ Unique identifier for a Member
+
+ site_role : UpdateSiteMemberRoleRequestBodySiteRole
+ The Site Role to assign
+
+ site_role_id : typing.Optional[str]
+ ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[SiteMember]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"sites/{jsonable_encoder(site_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "siteRole": site_role,
+ "siteRoleId": site_role_id,
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ SiteMember,
+ parse_obj_as(
+ type_=SiteMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/sites/resources/members/types/__init__.py b/src/webflow/resources/sites/resources/members/types/__init__.py
new file mode 100644
index 00000000..2da6958c
--- /dev/null
+++ b/src/webflow/resources/sites/resources/members/types/__init__.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .grant_site_member_request_body_site_role import GrantSiteMemberRequestBodySiteRole
+_dynamic_imports: typing.Dict[str, str] = {
+ "GrantSiteMemberRequestBodySiteRole": ".grant_site_member_request_body_site_role"
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = ["GrantSiteMemberRequestBodySiteRole"]
diff --git a/src/webflow/resources/sites/resources/members/types/grant_site_member_request_body_site_role.py b/src/webflow/resources/sites/resources/members/types/grant_site_member_request_body_site_role.py
new file mode 100644
index 00000000..c9b76213
--- /dev/null
+++ b/src/webflow/resources/sites/resources/members/types/grant_site_member_request_body_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GrantSiteMemberRequestBodySiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/resources/sites/resources/redirects/raw_client.py b/src/webflow/resources/sites/resources/redirects/raw_client.py
index d5abafed..77063ecd 100644
--- a/src/webflow/resources/sites/resources/redirects/raw_client.py
+++ b/src/webflow/resources/sites/resources/redirects/raw_client.py
@@ -180,9 +180,6 @@ def create(
"fromUrl": from_url,
"toUrl": to_url,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -419,9 +416,6 @@ def update(
"fromUrl": from_url,
"toUrl": to_url,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -658,9 +652,6 @@ async def create(
"fromUrl": from_url,
"toUrl": to_url,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -897,9 +888,6 @@ async def update(
"fromUrl": from_url,
"toUrl": to_url,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/sites/resources/robots_txt/raw_client.py b/src/webflow/resources/sites/resources/robots_txt/raw_client.py
index ac8d1fe4..3f476904 100644
--- a/src/webflow/resources/sites/resources/robots_txt/raw_client.py
+++ b/src/webflow/resources/sites/resources/robots_txt/raw_client.py
@@ -174,9 +174,6 @@ def put(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -300,9 +297,6 @@ def delete(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -424,9 +418,6 @@ def patch(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -656,9 +647,6 @@ async def put(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -782,9 +770,6 @@ async def delete(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -906,9 +891,6 @@ async def patch(
),
"sitemap": sitemap,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/sites/resources/scripts/client.py b/src/webflow/resources/sites/resources/scripts/client.py
index f7cddecd..69d1b5a3 100644
--- a/src/webflow/resources/sites/resources/scripts/client.py
+++ b/src/webflow/resources/sites/resources/scripts/client.py
@@ -38,6 +38,8 @@ def get_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -83,6 +85,8 @@ def upsert_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -189,6 +193,8 @@ def list_custom_code_blocks(
See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -254,6 +260,8 @@ async def get_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -307,6 +315,8 @@ async def upsert_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -431,6 +441,8 @@ async def list_custom_code_blocks(
See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
diff --git a/src/webflow/resources/sites/resources/scripts/raw_client.py b/src/webflow/resources/sites/resources/scripts/raw_client.py
index a5e6bb63..e60a37cd 100644
--- a/src/webflow/resources/sites/resources/scripts/raw_client.py
+++ b/src/webflow/resources/sites/resources/scripts/raw_client.py
@@ -40,6 +40,8 @@ def get_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -151,6 +153,8 @@ def upsert_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -186,9 +190,6 @@ def upsert_custom_code(
"lastUpdated": last_updated,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -380,6 +381,8 @@ def list_custom_code_blocks(
See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -500,6 +503,8 @@ async def get_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
@@ -611,6 +616,8 @@ async def upsert_custom_code(
To apply a script to a site or page, the script must first be registered to a site via the [Register Script](/data/reference/custom-code/custom-code/register-hosted) endpoints. Once registered, the script can be applied to a Site or Page using the appropriate endpoints. See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:write`
Parameters
@@ -646,9 +653,6 @@ async def upsert_custom_code(
"lastUpdated": last_updated,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -840,6 +844,8 @@ async def list_custom_code_blocks(
See the documentation on [working with Custom Code](/data/docs/custom-code) for more information.
+ Access to this endpoint requires a bearer token obtained from an [OAuth Code Grant Flow](/data/reference/oauth-app).
+
Required scope | `custom_code:read`
Parameters
diff --git a/src/webflow/resources/sites/types/__init__.py b/src/webflow/resources/sites/types/__init__.py
index 960aec63..85cd5337 100644
--- a/src/webflow/resources/sites/types/__init__.py
+++ b/src/webflow/resources/sites/types/__init__.py
@@ -7,7 +7,11 @@
if typing.TYPE_CHECKING:
from .sites_publish_response import SitesPublishResponse
-_dynamic_imports: typing.Dict[str, str] = {"SitesPublishResponse": ".sites_publish_response"}
+ from .sites_publish_response_publish_scope import SitesPublishResponsePublishScope
+_dynamic_imports: typing.Dict[str, str] = {
+ "SitesPublishResponse": ".sites_publish_response",
+ "SitesPublishResponsePublishScope": ".sites_publish_response_publish_scope",
+}
def __getattr__(attr_name: str) -> typing.Any:
@@ -31,4 +35,4 @@ def __dir__():
return sorted(lazy_attrs)
-__all__ = ["SitesPublishResponse"]
+__all__ = ["SitesPublishResponse", "SitesPublishResponsePublishScope"]
diff --git a/src/webflow/resources/sites/types/sites_publish_response.py b/src/webflow/resources/sites/types/sites_publish_response.py
index d32e70ce..18debc2c 100644
--- a/src/webflow/resources/sites/types/sites_publish_response.py
+++ b/src/webflow/resources/sites/types/sites_publish_response.py
@@ -7,6 +7,7 @@
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.serialization import FieldMetadata
from ....types.domain import Domain
+from .sites_publish_response_publish_scope import SitesPublishResponsePublishScope
class SitesPublishResponse(UniversalBaseModel):
@@ -20,6 +21,11 @@ class SitesPublishResponse(UniversalBaseModel):
FieldMetadata(alias="publishToWebflowSubdomain"),
pydantic.Field(alias="publishToWebflowSubdomain", description="Flag for publishing to webflow.io subdomain"),
] = None
+ publish_scope: typing_extensions.Annotated[
+ typing.Optional[SitesPublishResponsePublishScope],
+ FieldMetadata(alias="publishScope"),
+ pydantic.Field(alias="publishScope", description="Whether the site or an individual page was published"),
+ ] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/resources/sites/types/sites_publish_response_publish_scope.py b/src/webflow/resources/sites/types/sites_publish_response_publish_scope.py
new file mode 100644
index 00000000..11ce327a
--- /dev/null
+++ b/src/webflow/resources/sites/types/sites_publish_response_publish_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SitesPublishResponsePublishScope = typing.Union[typing.Literal["site", "page"], typing.Any]
diff --git a/src/webflow/resources/token/client.py b/src/webflow/resources/token/client.py
index d4712d7c..326643cf 100644
--- a/src/webflow/resources/token/client.py
+++ b/src/webflow/resources/token/client.py
@@ -56,7 +56,9 @@ def introspect(self, *, request_options: typing.Optional[RequestOptions] = None)
"""
Information about the authorization token
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from either a [Data Client App](/data/docs/data-clients/getting-started), a [Workspace token](/data/reference/authentication/workspace-token), or a [Site token](/data/reference/authentication/site-token).
+
+ The `application` object is only returned for tokens issued to an OAuth app. For tokens without an OAuth app, such as Site and Workspace API tokens (`grantType: client_credentials`), the response omits `application`.
Parameters
----------
@@ -136,7 +138,9 @@ async def introspect(self, *, request_options: typing.Optional[RequestOptions] =
"""
Information about the authorization token
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from either a [Data Client App](/data/docs/data-clients/getting-started), a [Workspace token](/data/reference/authentication/workspace-token), or a [Site token](/data/reference/authentication/site-token).
+
+ The `application` object is only returned for tokens issued to an OAuth app. For tokens without an OAuth app, such as Site and Workspace API tokens (`grantType: client_credentials`), the response omits `application`.
Parameters
----------
diff --git a/src/webflow/resources/token/raw_client.py b/src/webflow/resources/token/raw_client.py
index ea49a255..e80c4d2d 100644
--- a/src/webflow/resources/token/raw_client.py
+++ b/src/webflow/resources/token/raw_client.py
@@ -88,7 +88,9 @@ def introspect(self, *, request_options: typing.Optional[RequestOptions] = None)
"""
Information about the authorization token
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from either a [Data Client App](/data/docs/data-clients/getting-started), a [Workspace token](/data/reference/authentication/workspace-token), or a [Site token](/data/reference/authentication/site-token).
+
+ The `application` object is only returned for tokens issued to an OAuth app. For tokens without an OAuth app, such as Site and Workspace API tokens (`grantType: client_credentials`), the response omits `application`.
Parameters
----------
@@ -212,7 +214,9 @@ async def introspect(
"""
Information about the authorization token
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from either a [Data Client App](/data/docs/data-clients/getting-started), a [Workspace token](/data/reference/authentication/workspace-token), or a [Site token](/data/reference/authentication/site-token).
+
+ The `application` object is only returned for tokens issued to an OAuth app. For tokens without an OAuth app, such as Site and Workspace API tokens (`grantType: client_credentials`), the response omits `application`.
Parameters
----------
diff --git a/src/webflow/resources/webhooks/client.py b/src/webflow/resources/webhooks/client.py
index 43605265..28b5d567 100644
--- a/src/webflow/resources/webhooks/client.py
+++ b/src/webflow/resources/webhooks/client.py
@@ -82,7 +82,7 @@ def create(
Limit of 75 registrations per `triggerType`, per site.
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/data-clients/getting-started).
Required scope | `sites:write`
Parameters
@@ -300,7 +300,7 @@ async def create(
Limit of 75 registrations per `triggerType`, per site.
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/data-clients/getting-started).
Required scope | `sites:write`
Parameters
diff --git a/src/webflow/resources/webhooks/raw_client.py b/src/webflow/resources/webhooks/raw_client.py
index ca4462de..902e9c93 100644
--- a/src/webflow/resources/webhooks/raw_client.py
+++ b/src/webflow/resources/webhooks/raw_client.py
@@ -152,7 +152,7 @@ def create(
Limit of 75 registrations per `triggerType`, per site.
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/data-clients/getting-started).
Required scope | `sites:write`
Parameters
@@ -207,9 +207,6 @@ def create(
"lastTriggered": last_triggered,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
@@ -602,7 +599,7 @@ async def create(
Limit of 75 registrations per `triggerType`, per site.
- Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/getting-started-data-clients).
+ Access to this endpoint requires a bearer token from a [Data Client App](/data/docs/data-clients/getting-started).
Required scope | `sites:write`
Parameters
@@ -657,9 +654,6 @@ async def create(
"lastTriggered": last_triggered,
"createdOn": created_on,
},
- headers={
- "content-type": "application/json",
- },
request_options=request_options,
omit=OMIT,
)
diff --git a/src/webflow/resources/workspaces/__init__.py b/src/webflow/resources/workspaces/__init__.py
index dd54dbdc..e4600695 100644
--- a/src/webflow/resources/workspaces/__init__.py
+++ b/src/webflow/resources/workspaces/__init__.py
@@ -9,12 +9,24 @@
from .resources import (
AuditLogsGetWorkspaceAuditLogsRequestEventType,
AuditLogsGetWorkspaceAuditLogsRequestSortOrder,
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ MembersListMembersRequestRolesItem,
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ UpdateWorkspaceMemberRoleRequestBodyRole,
audit_logs,
+ members,
)
_dynamic_imports: typing.Dict[str, str] = {
"AuditLogsGetWorkspaceAuditLogsRequestEventType": ".resources",
"AuditLogsGetWorkspaceAuditLogsRequestSortOrder": ".resources",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole": ".resources",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole": ".resources",
+ "MembersListMembersRequestRolesItem": ".resources",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole": ".resources",
+ "UpdateWorkspaceMemberRoleRequestBodyRole": ".resources",
"audit_logs": ".resources",
+ "members": ".resources",
}
@@ -42,5 +54,11 @@ def __dir__():
__all__ = [
"AuditLogsGetWorkspaceAuditLogsRequestEventType",
"AuditLogsGetWorkspaceAuditLogsRequestSortOrder",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole",
+ "MembersListMembersRequestRolesItem",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole",
+ "UpdateWorkspaceMemberRoleRequestBodyRole",
"audit_logs",
+ "members",
]
diff --git a/src/webflow/resources/workspaces/client.py b/src/webflow/resources/workspaces/client.py
index 4b6aa917..77a9e259 100644
--- a/src/webflow/resources/workspaces/client.py
+++ b/src/webflow/resources/workspaces/client.py
@@ -5,10 +5,13 @@
import typing
from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.request_options import RequestOptions
+from ...types.workspaces import Workspaces
from .raw_client import AsyncRawWorkspacesClient, RawWorkspacesClient
if typing.TYPE_CHECKING:
from .resources.audit_logs.client import AsyncAuditLogsClient, AuditLogsClient
+ from .resources.members.client import AsyncMembersClient, MembersClient
class WorkspacesClient:
@@ -16,6 +19,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper):
self._raw_client = RawWorkspacesClient(client_wrapper=client_wrapper)
self._client_wrapper = client_wrapper
self._audit_logs: typing.Optional[AuditLogsClient] = None
+ self._members: typing.Optional[MembersClient] = None
@property
def with_raw_response(self) -> RawWorkspacesClient:
@@ -28,6 +32,38 @@ def with_raw_response(self) -> RawWorkspacesClient:
"""
return self._raw_client
+ def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> Workspaces:
+ """
+ List the workspaces the token can access, including each workspace's ID. Use this endpoint to find the `workspace_id` that other workspace endpoints require.
+
+ The response includes the workspaces granted to the token, plus the workspace that owns each site granted to the token. It returns only active workspaces (not archived, deleted, or draft) and isn't paginated.
+
+ This endpoint supports workspace API tokens and Data Client apps.
+
+ Required scope | `workspace:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Workspaces
+ A list of workspaces
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.list()
+ """
+ _response = self._raw_client.list(request_options=request_options)
+ return _response.data
+
@property
def audit_logs(self):
if self._audit_logs is None:
@@ -36,12 +72,21 @@ def audit_logs(self):
self._audit_logs = AuditLogsClient(client_wrapper=self._client_wrapper)
return self._audit_logs
+ @property
+ def members(self):
+ if self._members is None:
+ from .resources.members.client import MembersClient # noqa: E402
+
+ self._members = MembersClient(client_wrapper=self._client_wrapper)
+ return self._members
+
class AsyncWorkspacesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
self._raw_client = AsyncRawWorkspacesClient(client_wrapper=client_wrapper)
self._client_wrapper = client_wrapper
self._audit_logs: typing.Optional[AsyncAuditLogsClient] = None
+ self._members: typing.Optional[AsyncMembersClient] = None
@property
def with_raw_response(self) -> AsyncRawWorkspacesClient:
@@ -54,6 +99,46 @@ def with_raw_response(self) -> AsyncRawWorkspacesClient:
"""
return self._raw_client
+ async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> Workspaces:
+ """
+ List the workspaces the token can access, including each workspace's ID. Use this endpoint to find the `workspace_id` that other workspace endpoints require.
+
+ The response includes the workspaces granted to the token, plus the workspace that owns each site granted to the token. It returns only active workspaces (not archived, deleted, or draft) and isn't paginated.
+
+ This endpoint supports workspace API tokens and Data Client apps.
+
+ Required scope | `workspace:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ Workspaces
+ A list of workspaces
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.list()
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list(request_options=request_options)
+ return _response.data
+
@property
def audit_logs(self):
if self._audit_logs is None:
@@ -61,3 +146,11 @@ def audit_logs(self):
self._audit_logs = AsyncAuditLogsClient(client_wrapper=self._client_wrapper)
return self._audit_logs
+
+ @property
+ def members(self):
+ if self._members is None:
+ from .resources.members.client import AsyncMembersClient # noqa: E402
+
+ self._members = AsyncMembersClient(client_wrapper=self._client_wrapper)
+ return self._members
diff --git a/src/webflow/resources/workspaces/raw_client.py b/src/webflow/resources/workspaces/raw_client.py
index 8e986064..67fca106 100644
--- a/src/webflow/resources/workspaces/raw_client.py
+++ b/src/webflow/resources/workspaces/raw_client.py
@@ -1,13 +1,206 @@
# This file was auto-generated by Fern from our API Definition.
+import typing
+from json.decoder import JSONDecodeError
+
+from ...core.api_error import ApiError
from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ...core.http_response import AsyncHttpResponse, HttpResponse
+from ...core.parse_error import ParsingError
+from ...core.pydantic_utilities import parse_obj_as
+from ...core.request_options import RequestOptions
+from ...errors.forbidden_error import ForbiddenError
+from ...errors.internal_server_error import InternalServerError
+from ...errors.too_many_requests_error import TooManyRequestsError
+from ...errors.unauthorized_error import UnauthorizedError
+from ...types.error import Error
+from ...types.workspaces import Workspaces
+from pydantic import ValidationError
class RawWorkspacesClient:
def __init__(self, *, client_wrapper: SyncClientWrapper):
self._client_wrapper = client_wrapper
+ def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[Workspaces]:
+ """
+ List the workspaces the token can access, including each workspace's ID. Use this endpoint to find the `workspace_id` that other workspace endpoints require.
+
+ The response includes the workspaces granted to the token, plus the workspace that owns each site granted to the token. It returns only active workspaces (not archived, deleted, or draft) and isn't paginated.
+
+ This endpoint supports workspace API tokens and Data Client apps.
+
+ Required scope | `workspace:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[Workspaces]
+ A list of workspaces
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "workspaces",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Workspaces,
+ parse_obj_as(
+ type_=Workspaces, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
class AsyncRawWorkspacesClient:
def __init__(self, *, client_wrapper: AsyncClientWrapper):
self._client_wrapper = client_wrapper
+
+ async def list(self, *, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[Workspaces]:
+ """
+ List the workspaces the token can access, including each workspace's ID. Use this endpoint to find the `workspace_id` that other workspace endpoints require.
+
+ The response includes the workspaces granted to the token, plus the workspace that owns each site granted to the token. It returns only active workspaces (not archived, deleted, or draft) and isn't paginated.
+
+ This endpoint supports workspace API tokens and Data Client apps.
+
+ Required scope | `workspace:read`
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[Workspaces]
+ A list of workspaces
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "workspaces",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ Workspaces,
+ parse_obj_as(
+ type_=Workspaces, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/workspaces/resources/__init__.py b/src/webflow/resources/workspaces/resources/__init__.py
index a7bfbd20..f675895f 100644
--- a/src/webflow/resources/workspaces/resources/__init__.py
+++ b/src/webflow/resources/workspaces/resources/__init__.py
@@ -6,15 +6,28 @@
from importlib import import_module
if typing.TYPE_CHECKING:
- from . import audit_logs
+ from . import audit_logs, members
from .audit_logs import (
AuditLogsGetWorkspaceAuditLogsRequestEventType,
AuditLogsGetWorkspaceAuditLogsRequestSortOrder,
)
+ from .members import (
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ MembersListMembersRequestRolesItem,
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ UpdateWorkspaceMemberRoleRequestBodyRole,
+ )
_dynamic_imports: typing.Dict[str, str] = {
"AuditLogsGetWorkspaceAuditLogsRequestEventType": ".audit_logs",
"AuditLogsGetWorkspaceAuditLogsRequestSortOrder": ".audit_logs",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole": ".members",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole": ".members",
+ "MembersListMembersRequestRolesItem": ".members",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole": ".members",
+ "UpdateWorkspaceMemberRoleRequestBodyRole": ".members",
"audit_logs": ".audit_logs",
+ "members": ".members",
}
@@ -42,5 +55,11 @@ def __dir__():
__all__ = [
"AuditLogsGetWorkspaceAuditLogsRequestEventType",
"AuditLogsGetWorkspaceAuditLogsRequestSortOrder",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole",
+ "MembersListMembersRequestRolesItem",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole",
+ "UpdateWorkspaceMemberRoleRequestBodyRole",
"audit_logs",
+ "members",
]
diff --git a/src/webflow/resources/workspaces/resources/members/__init__.py b/src/webflow/resources/workspaces/resources/members/__init__.py
new file mode 100644
index 00000000..7df4eebc
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/__init__.py
@@ -0,0 +1,52 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .types import (
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ MembersListMembersRequestRolesItem,
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ UpdateWorkspaceMemberRoleRequestBodyRole,
+ )
+_dynamic_imports: typing.Dict[str, str] = {
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole": ".types",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole": ".types",
+ "MembersListMembersRequestRolesItem": ".types",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole": ".types",
+ "UpdateWorkspaceMemberRoleRequestBodyRole": ".types",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole",
+ "MembersListMembersRequestRolesItem",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole",
+ "UpdateWorkspaceMemberRoleRequestBodyRole",
+]
diff --git a/src/webflow/resources/workspaces/resources/members/client.py b/src/webflow/resources/workspaces/resources/members/client.py
new file mode 100644
index 00000000..fddfc2f0
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/client.py
@@ -0,0 +1,764 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.request_options import RequestOptions
+from .....types.invite_workspace_member_request_body import InviteWorkspaceMemberRequestBody
+from .....types.invite_workspace_member_response_body import InviteWorkspaceMemberResponseBody
+from .....types.workspace_bulk_role_change_response import WorkspaceBulkRoleChangeResponse
+from .....types.workspace_member import WorkspaceMember
+from .....types.workspace_members_response import WorkspaceMembersResponse
+from .raw_client import AsyncRawMembersClient, RawMembersClient
+from .types.bulk_update_workspace_member_roles_request_body_default_site_role import (
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+)
+from .types.bulk_update_workspace_member_roles_request_body_role import BulkUpdateWorkspaceMemberRolesRequestBodyRole
+from .types.members_list_members_request_roles_item import MembersListMembersRequestRolesItem
+from .types.update_workspace_member_role_request_body_default_site_role import (
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+)
+from .types.update_workspace_member_role_request_body_role import UpdateWorkspaceMemberRoleRequestBodyRole
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class MembersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawMembersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawMembersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawMembersClient
+ """
+ return self._raw_client
+
+ def list_members(
+ self,
+ workspace_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ roles: typing.Optional[
+ typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]
+ ] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceMembersResponse:
+ """
+ List the members and invites of a workspace. `invites` includes pending, declined, and expired invites.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:read`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Member or invite with this email address
+
+ roles : typing.Optional[typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]]
+ Comma-separated Workspace Roles to filter Members and invites by
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceMembersResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.members.list_members(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ email="email",
+ roles=["owner"],
+ cursor="cursor",
+ )
+ """
+ _response = self._raw_client.list_members(
+ workspace_id, limit=limit, email=email, roles=roles, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ def invite_member(
+ self,
+ workspace_id: str,
+ *,
+ request: InviteWorkspaceMemberRequestBody,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> InviteWorkspaceMemberResponseBody:
+ """
+ Invite a user to a workspace.
+
+ Set `workspaceRole` to `client` to invite a collaborator client to one Site. Client invites require exactly one `siteAccess` entry. Repeat the request with another Site to extend a compatible pending client invite.
+
+ Client invites create collaborator relationships only. This endpoint does not accept `sitePlan` or `canPublish`.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `workspaceRole` and `defaultSiteRole` together decide what the invitee can do and what role the dashboard shows for them once they accept. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `workspaceRole` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Admins always have access to every site in the workspace, so an Admin invite can't include a list of specific sites. For Member and Reviewer invites, you can either give the same role on all sites, or list specific sites. In both cases, use the pairings in the table above.
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ request : InviteWorkspaceMemberRequestBody
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ InviteWorkspaceMemberResponseBody
+ The request matched existing active access or a pending invite, or extended a compatible pending client invite.
+
+ Examples
+ --------
+ from webflow import Webflow, WithDefaultSiteRole
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.members.invite_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ request=WithDefaultSiteRole(
+ email="sam.okafor@example.com",
+ workspace_role="member",
+ default_site_role="designer",
+ ),
+ )
+ """
+ _response = self._raw_client.invite_member(workspace_id, request=request, request_options=request_options)
+ return _response.data
+
+ def remove_member(
+ self, workspace_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member from a workspace.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.members.remove_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ )
+ """
+ _response = self._raw_client.remove_member(workspace_id, member_id, request_options=request_options)
+ return _response.data
+
+ def update_member_role(
+ self,
+ workspace_id: str,
+ member_id: str,
+ *,
+ role: UpdateWorkspaceMemberRoleRequestBodyRole,
+ default_site_role: UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceMember:
+ """
+ Update a workspace member's role.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what the member can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ role : UpdateWorkspaceMemberRoleRequestBodyRole
+ The Workspace Role to assign
+
+ default_site_role : UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole
+ The Member's default Site Role. Required on every request — unlike the dashboard's own role editor, this endpoint never derives it from the Member's current role.
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceMember
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.members.update_member_role(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ role="admin",
+ default_site_role="site_manager",
+ )
+ """
+ _response = self._raw_client.update_member_role(
+ workspace_id,
+ member_id,
+ role=role,
+ default_site_role=default_site_role,
+ default_site_role_id=default_site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def bulk_update_member_roles(
+ self,
+ workspace_id: str,
+ *,
+ role: BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ default_site_role: BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ member_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ invite_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceBulkRoleChangeResponse:
+ """
+ Update the role of multiple workspace members and invites in one request. An invite can be updated regardless of status — pending, declined, or expired.
+
+ This answers `200` once the batch is applied, even when every item failed — a per-item problem is reported in `results` rather than failing the whole request. IDs are deduplicated before processing, so `results` can be shorter than the request; match a result back to your input by `id`, not by position.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what every id in the batch can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ role : BulkUpdateWorkspaceMemberRolesRequestBodyRole
+ The Workspace Role to assign to every id in the batch
+
+ default_site_role : BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole
+ The default Site Role to assign to every id in the batch
+
+ member_ids : typing.Optional[typing.Sequence[str]]
+ IDs of active Members to update
+
+ invite_ids : typing.Optional[typing.Sequence[str]]
+ IDs of invites to update, regardless of status
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceBulkRoleChangeResponse
+ Request was successful
+
+ Examples
+ --------
+ from webflow import Webflow
+
+ client = Webflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+ client.workspaces.members.bulk_update_member_roles(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_ids=["62fa5e47f6b8256f4641c5bd", "62fa5e47f6b8256f4641c5be"],
+ invite_ids=["63fa5e47f6b8256f4641c5bf"],
+ role="member",
+ default_site_role="designer",
+ )
+ """
+ _response = self._raw_client.bulk_update_member_roles(
+ workspace_id,
+ role=role,
+ default_site_role=default_site_role,
+ member_ids=member_ids,
+ invite_ids=invite_ids,
+ default_site_role_id=default_site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
+
+
+class AsyncMembersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawMembersClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawMembersClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawMembersClient
+ """
+ return self._raw_client
+
+ async def list_members(
+ self,
+ workspace_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ roles: typing.Optional[
+ typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]
+ ] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceMembersResponse:
+ """
+ List the members and invites of a workspace. `invites` includes pending, declined, and expired invites.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:read`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Member or invite with this email address
+
+ roles : typing.Optional[typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]]
+ Comma-separated Workspace Roles to filter Members and invites by
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceMembersResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.members.list_members(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ limit=1,
+ email="email",
+ roles=["owner"],
+ cursor="cursor",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.list_members(
+ workspace_id, limit=limit, email=email, roles=roles, cursor=cursor, request_options=request_options
+ )
+ return _response.data
+
+ async def invite_member(
+ self,
+ workspace_id: str,
+ *,
+ request: InviteWorkspaceMemberRequestBody,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> InviteWorkspaceMemberResponseBody:
+ """
+ Invite a user to a workspace.
+
+ Set `workspaceRole` to `client` to invite a collaborator client to one Site. Client invites require exactly one `siteAccess` entry. Repeat the request with another Site to extend a compatible pending client invite.
+
+ Client invites create collaborator relationships only. This endpoint does not accept `sitePlan` or `canPublish`.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `workspaceRole` and `defaultSiteRole` together decide what the invitee can do and what role the dashboard shows for them once they accept. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `workspaceRole` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Admins always have access to every site in the workspace, so an Admin invite can't include a list of specific sites. For Member and Reviewer invites, you can either give the same role on all sites, or list specific sites. In both cases, use the pairings in the table above.
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ request : InviteWorkspaceMemberRequestBody
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ InviteWorkspaceMemberResponseBody
+ The request matched existing active access or a pending invite, or extended a compatible pending client invite.
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow, WithDefaultSiteRole
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.members.invite_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ request=WithDefaultSiteRole(
+ email="sam.okafor@example.com",
+ workspace_role="member",
+ default_site_role="designer",
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.invite_member(workspace_id, request=request, request_options=request_options)
+ return _response.data
+
+ async def remove_member(
+ self, workspace_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> None:
+ """
+ Remove a member from a workspace.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.members.remove_member(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.remove_member(workspace_id, member_id, request_options=request_options)
+ return _response.data
+
+ async def update_member_role(
+ self,
+ workspace_id: str,
+ member_id: str,
+ *,
+ role: UpdateWorkspaceMemberRoleRequestBodyRole,
+ default_site_role: UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceMember:
+ """
+ Update a workspace member's role.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what the member can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ role : UpdateWorkspaceMemberRoleRequestBodyRole
+ The Workspace Role to assign
+
+ default_site_role : UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole
+ The Member's default Site Role. Required on every request — unlike the dashboard's own role editor, this endpoint never derives it from the Member's current role.
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceMember
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.members.update_member_role(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_id="62fa5e47f6b8256f4641c5bd",
+ role="admin",
+ default_site_role="site_manager",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update_member_role(
+ workspace_id,
+ member_id,
+ role=role,
+ default_site_role=default_site_role,
+ default_site_role_id=default_site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def bulk_update_member_roles(
+ self,
+ workspace_id: str,
+ *,
+ role: BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ default_site_role: BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ member_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ invite_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> WorkspaceBulkRoleChangeResponse:
+ """
+ Update the role of multiple workspace members and invites in one request. An invite can be updated regardless of status — pending, declined, or expired.
+
+ This answers `200` once the batch is applied, even when every item failed — a per-item problem is reported in `results` rather than failing the whole request. IDs are deduplicated before processing, so `results` can be shorter than the request; match a result back to your input by `id`, not by position.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what every id in the batch can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ role : BulkUpdateWorkspaceMemberRolesRequestBodyRole
+ The Workspace Role to assign to every id in the batch
+
+ default_site_role : BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole
+ The default Site Role to assign to every id in the batch
+
+ member_ids : typing.Optional[typing.Sequence[str]]
+ IDs of active Members to update
+
+ invite_ids : typing.Optional[typing.Sequence[str]]
+ IDs of invites to update, regardless of status
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ WorkspaceBulkRoleChangeResponse
+ Request was successful
+
+ Examples
+ --------
+ import asyncio
+
+ from webflow import AsyncWebflow
+
+ client = AsyncWebflow(
+ access_token="YOUR_ACCESS_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.workspaces.members.bulk_update_member_roles(
+ workspace_id="580e63e98c9a982ac9b8b741",
+ member_ids=["62fa5e47f6b8256f4641c5bd", "62fa5e47f6b8256f4641c5be"],
+ invite_ids=["63fa5e47f6b8256f4641c5bf"],
+ role="member",
+ default_site_role="designer",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.bulk_update_member_roles(
+ workspace_id,
+ role=role,
+ default_site_role=default_site_role,
+ member_ids=member_ids,
+ invite_ids=invite_ids,
+ default_site_role_id=default_site_role_id,
+ request_options=request_options,
+ )
+ return _response.data
diff --git a/src/webflow/resources/workspaces/resources/members/raw_client.py b/src/webflow/resources/workspaces/resources/members/raw_client.py
new file mode 100644
index 00000000..6fc3746f
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/raw_client.py
@@ -0,0 +1,1553 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from .....core.api_error import ApiError
+from .....core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from .....core.http_response import AsyncHttpResponse, HttpResponse
+from .....core.jsonable_encoder import jsonable_encoder
+from .....core.parse_error import ParsingError
+from .....core.pydantic_utilities import parse_obj_as
+from .....core.request_options import RequestOptions
+from .....core.serialization import convert_and_respect_annotation_metadata
+from .....errors.bad_request_error import BadRequestError
+from .....errors.conflict_error import ConflictError
+from .....errors.forbidden_error import ForbiddenError
+from .....errors.internal_server_error import InternalServerError
+from .....errors.not_found_error import NotFoundError
+from .....errors.too_many_requests_error import TooManyRequestsError
+from .....errors.unauthorized_error import UnauthorizedError
+from .....types.error import Error
+from .....types.invite_workspace_member_request_body import InviteWorkspaceMemberRequestBody
+from .....types.invite_workspace_member_response_body import InviteWorkspaceMemberResponseBody
+from .....types.workspace_bulk_role_change_response import WorkspaceBulkRoleChangeResponse
+from .....types.workspace_member import WorkspaceMember
+from .....types.workspace_members_response import WorkspaceMembersResponse
+from .types.bulk_update_workspace_member_roles_request_body_default_site_role import (
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+)
+from .types.bulk_update_workspace_member_roles_request_body_role import BulkUpdateWorkspaceMemberRolesRequestBodyRole
+from .types.members_list_members_request_roles_item import MembersListMembersRequestRolesItem
+from .types.update_workspace_member_role_request_body_default_site_role import (
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+)
+from .types.update_workspace_member_role_request_body_role import UpdateWorkspaceMemberRoleRequestBodyRole
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawMembersClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list_members(
+ self,
+ workspace_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ roles: typing.Optional[
+ typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]
+ ] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[WorkspaceMembersResponse]:
+ """
+ List the members and invites of a workspace. `invites` includes pending, declined, and expired invites.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:read`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Member or invite with this email address
+
+ roles : typing.Optional[typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]]
+ Comma-separated Workspace Roles to filter Members and invites by
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[WorkspaceMembersResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "limit": limit,
+ "email": email,
+ "roles": ",".join(map(str, roles)) if isinstance(roles, (list, tuple, set)) else roles,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceMembersResponse,
+ parse_obj_as(
+ type_=WorkspaceMembersResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def invite_member(
+ self,
+ workspace_id: str,
+ *,
+ request: InviteWorkspaceMemberRequestBody,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[InviteWorkspaceMemberResponseBody]:
+ """
+ Invite a user to a workspace.
+
+ Set `workspaceRole` to `client` to invite a collaborator client to one Site. Client invites require exactly one `siteAccess` entry. Repeat the request with another Site to extend a compatible pending client invite.
+
+ Client invites create collaborator relationships only. This endpoint does not accept `sitePlan` or `canPublish`.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `workspaceRole` and `defaultSiteRole` together decide what the invitee can do and what role the dashboard shows for them once they accept. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `workspaceRole` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Admins always have access to every site in the workspace, so an Admin invite can't include a list of specific sites. For Member and Reviewer invites, you can either give the same role on all sites, or list specific sites. In both cases, use the pairings in the table above.
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ request : InviteWorkspaceMemberRequestBody
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[InviteWorkspaceMemberResponseBody]
+ The request matched existing active access or a pending invite, or extended a compatible pending client invite.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json=convert_and_respect_annotation_metadata(
+ object_=request, annotation=InviteWorkspaceMemberRequestBody, direction="write"
+ ),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ InviteWorkspaceMemberResponseBody,
+ parse_obj_as(
+ type_=InviteWorkspaceMemberResponseBody, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def remove_member(
+ self, workspace_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[None]:
+ """
+ Remove a member from a workspace.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[None]
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return HttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def update_member_role(
+ self,
+ workspace_id: str,
+ member_id: str,
+ *,
+ role: UpdateWorkspaceMemberRoleRequestBodyRole,
+ default_site_role: UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[WorkspaceMember]:
+ """
+ Update a workspace member's role.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what the member can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ role : UpdateWorkspaceMemberRoleRequestBodyRole
+ The Workspace Role to assign
+
+ default_site_role : UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole
+ The Member's default Site Role. Required on every request — unlike the dashboard's own role editor, this endpoint never derives it from the Member's current role.
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[WorkspaceMember]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "role": role,
+ "defaultSiteRole": default_site_role,
+ "defaultSiteRoleId": default_site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceMember,
+ parse_obj_as(
+ type_=WorkspaceMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def bulk_update_member_roles(
+ self,
+ workspace_id: str,
+ *,
+ role: BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ default_site_role: BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ member_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ invite_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[WorkspaceBulkRoleChangeResponse]:
+ """
+ Update the role of multiple workspace members and invites in one request. An invite can be updated regardless of status — pending, declined, or expired.
+
+ This answers `200` once the batch is applied, even when every item failed — a per-item problem is reported in `results` rather than failing the whole request. IDs are deduplicated before processing, so `results` can be shorter than the request; match a result back to your input by `id`, not by position.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what every id in the batch can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ role : BulkUpdateWorkspaceMemberRolesRequestBodyRole
+ The Workspace Role to assign to every id in the batch
+
+ default_site_role : BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole
+ The default Site Role to assign to every id in the batch
+
+ member_ids : typing.Optional[typing.Sequence[str]]
+ IDs of active Members to update
+
+ invite_ids : typing.Optional[typing.Sequence[str]]
+ IDs of invites to update, regardless of status
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[WorkspaceBulkRoleChangeResponse]
+ Request was successful
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/bulk",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "memberIds": member_ids,
+ "inviteIds": invite_ids,
+ "role": role,
+ "defaultSiteRole": default_site_role,
+ "defaultSiteRoleId": default_site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceBulkRoleChangeResponse,
+ parse_obj_as(
+ type_=WorkspaceBulkRoleChangeResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawMembersClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list_members(
+ self,
+ workspace_id: str,
+ *,
+ limit: typing.Optional[int] = None,
+ email: typing.Optional[str] = None,
+ roles: typing.Optional[
+ typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]
+ ] = None,
+ cursor: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[WorkspaceMembersResponse]:
+ """
+ List the members and invites of a workspace. `invites` includes pending, declined, and expired invites.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:read`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ limit : typing.Optional[int]
+ Maximum number of records to return. Defaults to `50`, up to a maximum of `100`.
+
+ email : typing.Optional[str]
+ Filter to the Member or invite with this email address
+
+ roles : typing.Optional[typing.Union[MembersListMembersRequestRolesItem, typing.Sequence[MembersListMembersRequestRolesItem]]]
+ Comma-separated Workspace Roles to filter Members and invites by
+
+ cursor : typing.Optional[str]
+ Cursor returned in `pagination.nextCursor` of the previous response
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[WorkspaceMembersResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="GET",
+ params={
+ "limit": limit,
+ "email": email,
+ "roles": ",".join(map(str, roles)) if isinstance(roles, (list, tuple, set)) else roles,
+ "cursor": cursor,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceMembersResponse,
+ parse_obj_as(
+ type_=WorkspaceMembersResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def invite_member(
+ self,
+ workspace_id: str,
+ *,
+ request: InviteWorkspaceMemberRequestBody,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[InviteWorkspaceMemberResponseBody]:
+ """
+ Invite a user to a workspace.
+
+ Set `workspaceRole` to `client` to invite a collaborator client to one Site. Client invites require exactly one `siteAccess` entry. Repeat the request with another Site to extend a compatible pending client invite.
+
+ Client invites create collaborator relationships only. This endpoint does not accept `sitePlan` or `canPublish`.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `workspaceRole` and `defaultSiteRole` together decide what the invitee can do and what role the dashboard shows for them once they accept. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `workspaceRole` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Admins always have access to every site in the workspace, so an Admin invite can't include a list of specific sites. For Member and Reviewer invites, you can either give the same role on all sites, or list specific sites. In both cases, use the pairings in the table above.
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ request : InviteWorkspaceMemberRequestBody
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[InviteWorkspaceMemberResponseBody]
+ The request matched existing active access or a pending invite, or extended a compatible pending client invite.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json=convert_and_respect_annotation_metadata(
+ object_=request, annotation=InviteWorkspaceMemberRequestBody, direction="write"
+ ),
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ InviteWorkspaceMemberResponseBody,
+ parse_obj_as(
+ type_=InviteWorkspaceMemberResponseBody, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def remove_member(
+ self, workspace_id: str, member_id: str, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[None]:
+ """
+ Remove a member from a workspace.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[None]
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ return AsyncHttpResponse(response=_response, data=None)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def update_member_role(
+ self,
+ workspace_id: str,
+ member_id: str,
+ *,
+ role: UpdateWorkspaceMemberRoleRequestBodyRole,
+ default_site_role: UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[WorkspaceMember]:
+ """
+ Update a workspace member's role.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what the member can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ member_id : str
+ Unique identifier for a Member
+
+ role : UpdateWorkspaceMemberRoleRequestBodyRole
+ The Workspace Role to assign
+
+ default_site_role : UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole
+ The Member's default Site Role. Required on every request — unlike the dashboard's own role editor, this endpoint never derives it from the Member's current role.
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[WorkspaceMember]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/{jsonable_encoder(member_id)}",
+ base_url=self._client_wrapper.get_environment().base,
+ method="PATCH",
+ json={
+ "role": role,
+ "defaultSiteRole": default_site_role,
+ "defaultSiteRoleId": default_site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceMember,
+ parse_obj_as(
+ type_=WorkspaceMember, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def bulk_update_member_roles(
+ self,
+ workspace_id: str,
+ *,
+ role: BulkUpdateWorkspaceMemberRolesRequestBodyRole,
+ default_site_role: BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ member_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ invite_ids: typing.Optional[typing.Sequence[str]] = OMIT,
+ default_site_role_id: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[WorkspaceBulkRoleChangeResponse]:
+ """
+ Update the role of multiple workspace members and invites in one request. An invite can be updated regardless of status — pending, declined, or expired.
+
+ This answers `200` once the batch is applied, even when every item failed — a per-item problem is reported in `results` rather than failing the whole request. IDs are deduplicated before processing, so `results` can be shorter than the request; match a result back to your input by `id`, not by position.
+
+ This endpoint requires an Enterprise workspace.
+
+ Required scope | `workspace_members:write`
+
+ `role` and `defaultSiteRole` together decide what every id in the batch can do and what role the dashboard shows for them. Submit only one of the combinations below — the dashboard's own role picker never produces any other pairing.
+
+ | `role` | `defaultSiteRole` | Shown in the dashboard as |
+ |---|---|---|
+ | `admin` | `site_manager` | Admin |
+ | `member` | `site_manager` | Site manager |
+ | `member` | `designer` | Designer |
+ | `member` | `marketer` | Marketer |
+ | `member` | `content_editor` | Content editor |
+ | `member` | `custom` | The custom Site Role's own name. Requires `defaultSiteRoleId`. |
+ | `reviewer` | `reviewer` | Reviewer |
+
+ Parameters
+ ----------
+ workspace_id : str
+ Unique identifier for a Workspace. Find it with [List Workspaces](/data/reference/enterprise/workspace-management/list) (workspace API tokens and Data Client apps), or with `authorization.authorizedTo.workspaceIds` from [Get Authorization Info](/data/reference/token/introspect). With a site token, read `workspaceId` from [Get Site](/data/reference/sites/get).
+
+ role : BulkUpdateWorkspaceMemberRolesRequestBodyRole
+ The Workspace Role to assign to every id in the batch
+
+ default_site_role : BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole
+ The default Site Role to assign to every id in the batch
+
+ member_ids : typing.Optional[typing.Sequence[str]]
+ IDs of active Members to update
+
+ invite_ids : typing.Optional[typing.Sequence[str]]
+ IDs of invites to update, regardless of status
+
+ default_site_role_id : typing.Optional[str]
+ ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[WorkspaceBulkRoleChangeResponse]
+ Request was successful
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"workspaces/{jsonable_encoder(workspace_id)}/members/bulk",
+ base_url=self._client_wrapper.get_environment().base,
+ method="POST",
+ json={
+ "memberIds": member_ids,
+ "inviteIds": invite_ids,
+ "role": role,
+ "defaultSiteRole": default_site_role,
+ "defaultSiteRoleId": default_site_role_id,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ WorkspaceBulkRoleChangeResponse,
+ parse_obj_as(
+ type_=WorkspaceBulkRoleChangeResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 403:
+ raise ForbiddenError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ typing.Any,
+ parse_obj_as(
+ type_=typing.Any, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 429:
+ raise TooManyRequestsError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 500:
+ raise InternalServerError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ Error,
+ parse_obj_as(
+ type_=Error, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/webflow/resources/workspaces/resources/members/types/__init__.py b/src/webflow/resources/workspaces/resources/members/types/__init__.py
new file mode 100644
index 00000000..c45246d3
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/__init__.py
@@ -0,0 +1,54 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
+import typing
+from importlib import import_module
+
+if typing.TYPE_CHECKING:
+ from .bulk_update_workspace_member_roles_request_body_default_site_role import (
+ BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole,
+ )
+ from .bulk_update_workspace_member_roles_request_body_role import BulkUpdateWorkspaceMemberRolesRequestBodyRole
+ from .members_list_members_request_roles_item import MembersListMembersRequestRolesItem
+ from .update_workspace_member_role_request_body_default_site_role import (
+ UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole,
+ )
+ from .update_workspace_member_role_request_body_role import UpdateWorkspaceMemberRoleRequestBodyRole
+_dynamic_imports: typing.Dict[str, str] = {
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole": ".bulk_update_workspace_member_roles_request_body_default_site_role",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole": ".bulk_update_workspace_member_roles_request_body_role",
+ "MembersListMembersRequestRolesItem": ".members_list_members_request_roles_item",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole": ".update_workspace_member_role_request_body_default_site_role",
+ "UpdateWorkspaceMemberRoleRequestBodyRole": ".update_workspace_member_role_request_body_role",
+}
+
+
+def __getattr__(attr_name: str) -> typing.Any:
+ module_name = _dynamic_imports.get(attr_name)
+ if module_name is None:
+ raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}")
+ try:
+ module = import_module(module_name, __package__)
+ if module_name == f".{attr_name}":
+ return module
+ else:
+ return getattr(module, attr_name)
+ except ImportError as e:
+ raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e
+ except AttributeError as e:
+ raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e
+
+
+def __dir__():
+ lazy_attrs = list(_dynamic_imports.keys())
+ return sorted(lazy_attrs)
+
+
+__all__ = [
+ "BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole",
+ "BulkUpdateWorkspaceMemberRolesRequestBodyRole",
+ "MembersListMembersRequestRolesItem",
+ "UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole",
+ "UpdateWorkspaceMemberRoleRequestBodyRole",
+]
diff --git a/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_default_site_role.py b/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_default_site_role.py
new file mode 100644
index 00000000..28d3df76
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_default_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+BulkUpdateWorkspaceMemberRolesRequestBodyDefaultSiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_role.py b/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_role.py
new file mode 100644
index 00000000..dafc8a68
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/bulk_update_workspace_member_roles_request_body_role.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+BulkUpdateWorkspaceMemberRolesRequestBodyRole = typing.Union[typing.Literal["admin", "member", "reviewer"], typing.Any]
diff --git a/src/webflow/resources/workspaces/resources/members/types/members_list_members_request_roles_item.py b/src/webflow/resources/workspaces/resources/members/types/members_list_members_request_roles_item.py
new file mode 100644
index 00000000..a44d308e
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/members_list_members_request_roles_item.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+MembersListMembersRequestRolesItem = typing.Union[
+ typing.Literal["owner", "admin", "member", "guest", "reviewer", "client"], typing.Any
+]
diff --git a/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_default_site_role.py b/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_default_site_role.py
new file mode 100644
index 00000000..64e7bf62
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_default_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+UpdateWorkspaceMemberRoleRequestBodyDefaultSiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_role.py b/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_role.py
new file mode 100644
index 00000000..cec75d82
--- /dev/null
+++ b/src/webflow/resources/workspaces/resources/members/types/update_workspace_member_role_request_body_role.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+UpdateWorkspaceMemberRoleRequestBodyRole = typing.Union[typing.Literal["admin", "member", "reviewer"], typing.Any]
diff --git a/src/webflow/types/__init__.py b/src/webflow/types/__init__.py
index 88c0cbd9..716834b3 100644
--- a/src/webflow/types/__init__.py
+++ b/src/webflow/types/__init__.py
@@ -6,6 +6,64 @@
from importlib import import_module
if typing.TYPE_CHECKING:
+ from .aeo_answer_engine import AeoAnswerEngine
+ from .aeo_answer_engine_key import AeoAnswerEngineKey
+ from .aeo_answer_engines_filter import AeoAnswerEnginesFilter
+ from .aeo_bot_category import AeoBotCategory
+ from .aeo_bot_provider import AeoBotProvider
+ from .aeo_bot_traffic_filter import AeoBotTrafficFilter
+ from .aeo_bot_traffic_page import AeoBotTrafficPage
+ from .aeo_bot_traffic_pages_response import AeoBotTrafficPagesResponse
+ from .aeo_bot_traffic_response import AeoBotTrafficResponse
+ from .aeo_brand_names import AeoBrandNames
+ from .aeo_citation_filter import AeoCitationFilter
+ from .aeo_citation_rate_response import AeoCitationRateResponse
+ from .aeo_citation_rate_response_breakdown import AeoCitationRateResponseBreakdown
+ from .aeo_citation_rate_response_data_item import AeoCitationRateResponseDataItem
+ from .aeo_citations_response import AeoCitationsResponse
+ from .aeo_citations_response_data_item import AeoCitationsResponseDataItem
+ from .aeo_cited_domain_paths_response import AeoCitedDomainPathsResponse
+ from .aeo_cited_path import AeoCitedPath
+ from .aeo_competitor_citations_response import AeoCompetitorCitationsResponse
+ from .aeo_competitor_citations_response_data_item import AeoCompetitorCitationsResponseDataItem
+ from .aeo_owned_cited_path import AeoOwnedCitedPath
+ from .aeo_page import AeoPage
+ from .aeo_page_citation_share_response import AeoPageCitationShareResponse
+ from .aeo_page_citation_share_response_data_item import AeoPageCitationShareResponseDataItem
+ from .aeo_page_citation_share_response_filter import AeoPageCitationShareResponseFilter
+ from .aeo_page_citation_share_response_page import AeoPageCitationShareResponsePage
+ from .aeo_prompt import AeoPrompt
+ from .aeo_prompt_execution import AeoPromptExecution
+ from .aeo_prompt_execution_list_response import AeoPromptExecutionListResponse
+ from .aeo_prompt_execution_prompt_status import AeoPromptExecutionPromptStatus
+ from .aeo_prompt_execution_status import AeoPromptExecutionStatus
+ from .aeo_prompt_execution_topic import AeoPromptExecutionTopic
+ from .aeo_prompt_ids_filter import AeoPromptIdsFilter
+ from .aeo_prompt_label import AeoPromptLabel
+ from .aeo_prompt_list_response import AeoPromptListResponse
+ from .aeo_prompt_status import AeoPromptStatus
+ from .aeo_prompt_topic import AeoPromptTopic
+ from .aeo_timeseries import AeoTimeseries
+ from .aeo_timeseries_granularity_period import AeoTimeseriesGranularityPeriod
+ from .aeo_top_cited_domains_response import AeoTopCitedDomainsResponse
+ from .aeo_top_cited_domains_response_data_item import AeoTopCitedDomainsResponseDataItem
+ from .aeo_topic_engine_filter import AeoTopicEngineFilter
+ from .aeo_topic_ids_filter import AeoTopicIdsFilter
+ from .aeo_topic_label import AeoTopicLabel
+ from .aeo_visibility_breakdown_response import AeoVisibilityBreakdownResponse
+ from .aeo_visibility_breakdown_response_breakdown import AeoVisibilityBreakdownResponseBreakdown
+ from .aeo_visibility_breakdown_response_data_item import AeoVisibilityBreakdownResponseDataItem
+ from .aeo_visibility_filter import AeoVisibilityFilter
+ from .aeo_visibility_response import AeoVisibilityResponse
+ from .aeo_visibility_response_data_item import AeoVisibilityResponseDataItem
+ from .aeo_visibility_score import AeoVisibilityScore
+ from .aeo_window import AeoWindow
+ from .analyze_bucket_time_zone import AnalyzeBucketTimeZone
+ from .analyze_daily_bucketing import AnalyzeDailyBucketing
+ from .analyze_daily_timeseries_query import AnalyzeDailyTimeseriesQuery
+ from .analyze_filter_operators import AnalyzeFilterOperators
+ from .analyze_time_on_page_bucketing import AnalyzeTimeOnPageBucketing
+ from .analyze_window import AnalyzeWindow
from .application import Application
from .asset import Asset
from .asset_folder import AssetFolder
@@ -19,8 +77,18 @@
from .authorization_authorization_authorized_to import AuthorizationAuthorizationAuthorizedTo
from .authorized_user import AuthorizedUser
from .bad_request_error_body import BadRequestErrorBody
+ from .branch_created_webhook import BranchCreatedWebhook
+ from .branch_created_webhook_payload import BranchCreatedWebhookPayload
+ from .branch_deleted_webhook import BranchDeletedWebhook
+ from .branch_deleted_webhook_payload import BranchDeletedWebhookPayload
+ from .branch_merged_webhook import BranchMergedWebhook
+ from .branch_merged_webhook_payload import BranchMergedWebhookPayload
+ from .browser_window_height_data_point import BrowserWindowHeightDataPoint
+ from .browser_window_height_filter import BrowserWindowHeightFilter
+ from .browser_window_height_response import BrowserWindowHeightResponse
from .bulk_collection_item import BulkCollectionItem
from .bulk_collection_item_field_data import BulkCollectionItemFieldData
+ from .collaborator_client_invite import CollaboratorClientInvite
from .collection import Collection
from .collection_item import CollectionItem
from .collection_item_changed import CollectionItemChanged
@@ -48,6 +116,7 @@
from .comment_payload import CommentPayload
from .comment_payload_author import CommentPayloadAuthor
from .comment_payload_mentioned_users_item import CommentPayloadMentionedUsersItem
+ from .comment_payload_type import CommentPayloadType
from .comment_reply import CommentReply
from .comment_reply_author import CommentReplyAuthor
from .comment_reply_list import CommentReplyList
@@ -68,12 +137,27 @@
from .component_node import ComponentNode
from .component_properties import ComponentProperties
from .component_property import ComponentProperty
- from .component_property_type import ComponentPropertyType
+ from .component_property_text import ComponentPropertyText
+ from .component_property_text_type import ComponentPropertyTextType
from .conflict import Conflict
from .custom_code_block import CustomCodeBlock
from .custom_code_block_type import CustomCodeBlockType
from .custom_code_hosted_response import CustomCodeHostedResponse
from .custom_code_inline_response import CustomCodeInlineResponse
+ from .custom_font import CustomFont
+ from .custom_font_axis import CustomFontAxis
+ from .custom_font_batch_create_response import CustomFontBatchCreateResponse
+ from .custom_font_batch_create_response_created_item import CustomFontBatchCreateResponseCreatedItem
+ from .custom_font_batch_create_response_failed_item import CustomFontBatchCreateResponseFailedItem
+ from .custom_font_batch_delete_response import CustomFontBatchDeleteResponse
+ from .custom_font_batch_delete_response_deleted_item import CustomFontBatchDeleteResponseDeletedItem
+ from .custom_font_batch_delete_response_failed_item import CustomFontBatchDeleteResponseFailedItem
+ from .custom_font_create_response import CustomFontCreateResponse
+ from .custom_font_font_display import CustomFontFontDisplay
+ from .custom_font_format import CustomFontFormat
+ from .custom_font_upload import CustomFontUpload
+ from .custom_font_upload_fields import CustomFontUploadFields
+ from .custom_fonts import CustomFonts
from .custom_role import CustomRole
from .custom_role_audit_log_item import CustomRoleAuditLogItem
from .custom_role_audit_log_item_event_sub_type import CustomRoleAuditLogItemEventSubType
@@ -85,12 +169,10 @@
from .error_code import ErrorCode
from .field import Field
from .field_create import FieldCreate
+ from .field_group import FieldGroup
from .field_type import FieldType
from .field_validations import FieldValidations
from .field_validations_additional_properties import FieldValidationsAdditionalProperties
- from .field_validations_additional_properties_additional_properties import (
- FieldValidationsAdditionalPropertiesAdditionalProperties,
- )
from .forbidden_error_body import ForbiddenErrorBody
from .form import Form
from .form_field import FormField
@@ -104,14 +186,63 @@
from .form_submission_trigger_payload import FormSubmissionTriggerPayload
from .form_submission_trigger_payload_schema_item import FormSubmissionTriggerPayloadSchemaItem
from .form_submission_trigger_payload_schema_item_field_type import FormSubmissionTriggerPayloadSchemaItemFieldType
+ from .goal import Goal
+ from .goal_category import GoalCategory
+ from .goal_integration_source import GoalIntegrationSource
+ from .goal_performance_data_point import GoalPerformanceDataPoint
+ from .goal_performance_filter import GoalPerformanceFilter
+ from .goal_performance_response import GoalPerformanceResponse
+ from .goal_scope import GoalScope
+ from .goal_type import GoalType
+ from .goal_value_type import GoalValueType
+ from .goals_response import GoalsResponse
+ from .google_tag_id import GoogleTagId
+ from .google_tag_ids import GoogleTagIds
from .image_node import ImageNode
from .image_node_image import ImageNodeImage
+ from .insert_collection_item import InsertCollectionItem
+ from .insert_collection_item_field_data import InsertCollectionItemFieldData
+ from .inserted_collection_item import InsertedCollectionItem
+ from .inserted_collection_item_field_data import InsertedCollectionItemFieldData
from .invalid_domain import InvalidDomain
from .invalid_scopes import InvalidScopes
from .inventory_item import InventoryItem
from .inventory_item_inventory_type import InventoryItemInventoryType
+ from .invite_workspace_member_request_body import InviteWorkspaceMemberRequestBody
+ from .invite_workspace_member_request_body_default_site_role_default_site_role import (
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole,
+ )
+ from .invite_workspace_member_request_body_default_site_role_workspace_role import (
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole,
+ )
+ from .invite_workspace_member_request_body_email_site_access_item import (
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItem,
+ )
+ from .invite_workspace_member_request_body_email_site_access_item_site_role import (
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole,
+ )
+ from .invite_workspace_member_request_body_one_site_access_item import (
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItem,
+ )
+ from .invite_workspace_member_request_body_one_site_access_item_site_role import (
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole,
+ )
+ from .invite_workspace_member_request_body_one_workspace_role import (
+ InviteWorkspaceMemberRequestBodyOneWorkspaceRole,
+ )
+ from .invite_workspace_member_response_body import (
+ InviteWorkspaceMemberResponseBody,
+ InviteWorkspaceMemberResponseBody_Active,
+ InviteWorkspaceMemberResponseBody_Invited,
+ )
+ from .invite_workspace_member_response_body_active import InviteWorkspaceMemberResponseBodyActive
+ from .invited import Invited
+ from .items_list_items_live_request_created_on import ItemsListItemsLiveRequestCreatedOn
from .items_list_items_live_request_last_published import ItemsListItemsLiveRequestLastPublished
+ from .items_list_items_live_request_last_updated import ItemsListItemsLiveRequestLastUpdated
+ from .items_list_items_request_created_on import ItemsListItemsRequestCreatedOn
from .items_list_items_request_last_published import ItemsListItemsRequestLastPublished
+ from .items_list_items_request_last_updated import ItemsListItemsRequestLastUpdated
from .list_custom_code_blocks import ListCustomCodeBlocks
from .locale import Locale
from .locales import Locales
@@ -131,6 +262,48 @@
)
from .not_enterprise_plan_site import NotEnterprisePlanSite
from .not_enterprise_plan_workspace import NotEnterprisePlanWorkspace
+ from .optimization import Optimization
+ from .optimization_audience_insights_response import OptimizationAudienceInsightsResponse
+ from .optimization_dimension_values_response import OptimizationDimensionValuesResponse
+ from .optimization_dimensions_response import OptimizationDimensionsResponse
+ from .optimization_goals_response import OptimizationGoalsResponse
+ from .optimization_interval import OptimizationInterval
+ from .optimization_list_response import OptimizationListResponse
+ from .optimization_status import OptimizationStatus
+ from .optimization_summary_response import OptimizationSummaryResponse
+ from .optimization_trend_response import OptimizationTrendResponse
+ from .optimization_type import OptimizationType
+ from .optimize_applied_filter import OptimizeAppliedFilter
+ from .optimize_applied_filter_entry import OptimizeAppliedFilterEntry
+ from .optimize_audience_segment import OptimizeAudienceSegment
+ from .optimize_audience_variation import OptimizeAudienceVariation
+ from .optimize_current_goal import OptimizeCurrentGoal
+ from .optimize_dimension import OptimizeDimension
+ from .optimize_dimension_value import OptimizeDimensionValue
+ from .optimize_filter import OptimizeFilter
+ from .optimize_filter_operators import OptimizeFilterOperators
+ from .optimize_goal import OptimizeGoal
+ from .optimize_goal_counting_method import OptimizeGoalCountingMethod
+ from .optimize_goal_metric_type import OptimizeGoalMetricType
+ from .optimize_goal_scope import OptimizeGoalScope
+ from .optimize_goal_source import OptimizeGoalSource
+ from .optimize_non_webflow_site_resource import OptimizeNonWebflowSiteResource
+ from .optimize_reference import OptimizeReference
+ from .optimize_reference_list_response import OptimizeReferenceListResponse
+ from .optimize_reference_resource import (
+ OptimizeReferenceResource,
+ OptimizeReferenceResource_OptimizeSite,
+ OptimizeReferenceResource_WebflowSite,
+ )
+ from .optimize_report_goal import OptimizeReportGoal
+ from .optimize_report_interval_end import OptimizeReportIntervalEnd
+ from .optimize_report_interval_start import OptimizeReportIntervalStart
+ from .optimize_trend_direction import OptimizeTrendDirection
+ from .optimize_trend_granularity import OptimizeTrendGranularity
+ from .optimize_trend_point import OptimizeTrendPoint
+ from .optimize_variation_comparison import OptimizeVariationComparison
+ from .optimize_variation_result import OptimizeVariationResult
+ from .optimize_webflow_site_resource import OptimizeWebflowSiteResource
from .option_field import OptionField
from .order import Order
from .order_address import OrderAddress
@@ -161,6 +334,11 @@
from .page_created_webhook_payload import PageCreatedWebhookPayload
from .page_deleted_webhook import PageDeletedWebhook
from .page_deleted_webhook_payload import PageDeletedWebhookPayload
+ from .page_flow_direction import PageFlowDirection
+ from .page_flow_filter import PageFlowFilter
+ from .page_flow_metric_scope import PageFlowMetricScope
+ from .page_flow_response import PageFlowResponse
+ from .page_flow_row import PageFlowRow
from .page_list import PageList
from .page_metadata_updated_webhook import PageMetadataUpdatedWebhook
from .page_metadata_updated_webhook_payload import PageMetadataUpdatedWebhookPayload
@@ -170,6 +348,7 @@
from .payload import Payload
from .payload_field_data import PayloadFieldData
from .paypal_details import PaypalDetails
+ from .person_actor import PersonActor
from .product import Product
from .product_and_sk_us import ProductAndSkUs
from .product_and_sk_us_list import ProductAndSkUsList
@@ -189,6 +368,9 @@
from .script_apply_list import ScriptApplyList
from .script_apply_location import ScriptApplyLocation
from .scripts import Scripts
+ from .scroll_depth_data_point import ScrollDepthDataPoint
+ from .scroll_depth_filter import ScrollDepthFilter
+ from .scroll_depth_response import ScrollDepthResponse
from .search_button_node import SearchButtonNode
from .search_button_node_write import SearchButtonNodeWrite
from .select import Select
@@ -201,11 +383,19 @@
from .single_locale_created_payload_field_data import SingleLocaleCreatedPayloadFieldData
from .site import Site
from .site_activity_log_item import SiteActivityLogItem
+ from .site_activity_log_item_actor_type import SiteActivityLogItemActorType
from .site_activity_log_item_event import SiteActivityLogItemEvent
from .site_activity_log_item_resource_operation import SiteActivityLogItemResourceOperation
+ from .site_activity_log_item_source import SiteActivityLogItemSource
from .site_activity_log_item_user import SiteActivityLogItemUser
from .site_activity_log_response import SiteActivityLogResponse
from .site_data_collection_type import SiteDataCollectionType
+ from .site_member import SiteMember
+ from .site_member_roles import SiteMemberRoles
+ from .site_member_roles_site import SiteMemberRolesSite
+ from .site_member_roles_workspace import SiteMemberRolesWorkspace
+ from .site_members_response import SiteMembersResponse
+ from .site_members_response_pagination import SiteMembersResponsePagination
from .site_membership import SiteMembership
from .site_membership_audit_log_item import SiteMembershipAuditLogItem
from .site_membership_audit_log_item_event_sub_type import SiteMembershipAuditLogItemEventSubType
@@ -214,6 +404,7 @@
from .site_plan_name import SitePlanName
from .site_publish import SitePublish
from .site_publish_payload import SitePublishPayload
+ from .site_publish_payload_publish_scope import SitePublishPayloadPublishScope
from .sites import Sites
from .sku import Sku
from .sku_field_data import SkuFieldData
@@ -243,7 +434,36 @@
from .text_node import TextNode
from .text_node_text import TextNodeText
from .text_node_write import TextNodeWrite
+ from .time_on_page_data_point import TimeOnPageDataPoint
+ from .time_on_page_filter import TimeOnPageFilter
+ from .time_on_page_granularity_period import TimeOnPageGranularityPeriod
+ from .time_on_page_metric_scope import TimeOnPageMetricScope
+ from .time_on_page_response import TimeOnPageResponse
+ from .time_on_page_timeseries_query import TimeOnPageTimeseriesQuery
+ from .token_actor import TokenActor
+ from .top_dimensions_dimension import TopDimensionsDimension
+ from .top_dimensions_filter import TopDimensionsFilter
+ from .top_dimensions_metric_scope import TopDimensionsMetricScope
+ from .top_dimensions_response import TopDimensionsResponse
+ from .top_dimensions_row import TopDimensionsRow
+ from .top_events_cms_context_entry import TopEventsCmsContextEntry
+ from .top_events_component_context_entry import TopEventsComponentContextEntry
+ from .top_events_filter import TopEventsFilter
+ from .top_events_response import TopEventsResponse
+ from .top_events_row import TopEventsRow
+ from .top_events_timeseries_point import TopEventsTimeseriesPoint
+ from .top_pages_filter import TopPagesFilter
+ from .top_pages_response import TopPagesResponse
+ from .top_pages_row import TopPagesRow
+ from .top_pages_sort_by import TopPagesSortBy
+ from .top_pages_timeseries_point import TopPagesTimeseriesPoint
+ from .traffic_data_point import TrafficDataPoint
+ from .traffic_filter import TrafficFilter
+ from .traffic_metric_scope import TrafficMetricScope
+ from .traffic_response import TrafficResponse
from .trigger_type import TriggerType
+ from .update_site_member_role_request_body import UpdateSiteMemberRoleRequestBody
+ from .update_site_member_role_request_body_site_role import UpdateSiteMemberRoleRequestBodySiteRole
from .updated_order import UpdatedOrder
from .user_access import UserAccess
from .user_access_audit_log_item import UserAccessAuditLogItem
@@ -251,6 +471,14 @@
from .webhook import Webhook
from .webhook_filter import WebhookFilter
from .webhook_list import WebhookList
+ from .with_default_site_role import WithDefaultSiteRole
+ from .with_site_access import WithSiteAccess
+ from .workspace import Workspace
+ from .workspace_audit_log_actor import (
+ WorkspaceAuditLogActor,
+ WorkspaceAuditLogActor_Person,
+ WorkspaceAuditLogActor_Token,
+ )
from .workspace_audit_log_item import (
WorkspaceAuditLogItem,
WorkspaceAuditLogItem_CustomRole,
@@ -260,8 +488,6 @@
WorkspaceAuditLogItem_WorkspaceMembership,
WorkspaceAuditLogItem_WorkspaceSetting,
)
- from .workspace_audit_log_item_actor import WorkspaceAuditLogItemActor
- from .workspace_audit_log_item_payload_setting_change_method import WorkspaceAuditLogItemPayloadSettingChangeMethod
from .workspace_audit_log_item_payload_site_membership_granular_access import (
WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess,
)
@@ -299,13 +525,93 @@
)
from .workspace_audit_log_item_workspace import WorkspaceAuditLogItemWorkspace
from .workspace_audit_log_response import WorkspaceAuditLogResponse
+ from .workspace_bulk_role_change_response import WorkspaceBulkRoleChangeResponse
+ from .workspace_bulk_role_change_response_results_item import WorkspaceBulkRoleChangeResponseResultsItem
+ from .workspace_bulk_role_change_response_results_item_error import WorkspaceBulkRoleChangeResponseResultsItemError
+ from .workspace_bulk_role_change_response_results_item_error_code import (
+ WorkspaceBulkRoleChangeResponseResultsItemErrorCode,
+ )
+ from .workspace_bulk_role_change_response_results_item_outcome import (
+ WorkspaceBulkRoleChangeResponseResultsItemOutcome,
+ )
+ from .workspace_bulk_role_change_response_results_item_type import WorkspaceBulkRoleChangeResponseResultsItemType
+ from .workspace_bulk_role_change_response_summary import WorkspaceBulkRoleChangeResponseSummary
from .workspace_invitation import WorkspaceInvitation
from .workspace_invitation_audit_log_item import WorkspaceInvitationAuditLogItem
from .workspace_invitation_audit_log_item_event_sub_type import WorkspaceInvitationAuditLogItemEventSubType
+ from .workspace_invite import WorkspaceInvite
+ from .workspace_invite_default_site_role import WorkspaceInviteDefaultSiteRole
+ from .workspace_invite_role import WorkspaceInviteRole
+ from .workspace_invite_status import WorkspaceInviteStatus
+ from .workspace_member import WorkspaceMember
+ from .workspace_member_roles import WorkspaceMemberRoles
+ from .workspace_member_roles_site import WorkspaceMemberRolesSite
+ from .workspace_member_roles_workspace import WorkspaceMemberRolesWorkspace
+ from .workspace_members_response import WorkspaceMembersResponse
+ from .workspace_members_response_pagination import WorkspaceMembersResponsePagination
from .workspace_membership import WorkspaceMembership
from .workspace_membership_audit_log_item import WorkspaceMembershipAuditLogItem
from .workspace_membership_audit_log_item_event_sub_type import WorkspaceMembershipAuditLogItemEventSubType
+ from .workspaces import Workspaces
_dynamic_imports: typing.Dict[str, str] = {
+ "AeoAnswerEngine": ".aeo_answer_engine",
+ "AeoAnswerEngineKey": ".aeo_answer_engine_key",
+ "AeoAnswerEnginesFilter": ".aeo_answer_engines_filter",
+ "AeoBotCategory": ".aeo_bot_category",
+ "AeoBotProvider": ".aeo_bot_provider",
+ "AeoBotTrafficFilter": ".aeo_bot_traffic_filter",
+ "AeoBotTrafficPage": ".aeo_bot_traffic_page",
+ "AeoBotTrafficPagesResponse": ".aeo_bot_traffic_pages_response",
+ "AeoBotTrafficResponse": ".aeo_bot_traffic_response",
+ "AeoBrandNames": ".aeo_brand_names",
+ "AeoCitationFilter": ".aeo_citation_filter",
+ "AeoCitationRateResponse": ".aeo_citation_rate_response",
+ "AeoCitationRateResponseBreakdown": ".aeo_citation_rate_response_breakdown",
+ "AeoCitationRateResponseDataItem": ".aeo_citation_rate_response_data_item",
+ "AeoCitationsResponse": ".aeo_citations_response",
+ "AeoCitationsResponseDataItem": ".aeo_citations_response_data_item",
+ "AeoCitedDomainPathsResponse": ".aeo_cited_domain_paths_response",
+ "AeoCitedPath": ".aeo_cited_path",
+ "AeoCompetitorCitationsResponse": ".aeo_competitor_citations_response",
+ "AeoCompetitorCitationsResponseDataItem": ".aeo_competitor_citations_response_data_item",
+ "AeoOwnedCitedPath": ".aeo_owned_cited_path",
+ "AeoPage": ".aeo_page",
+ "AeoPageCitationShareResponse": ".aeo_page_citation_share_response",
+ "AeoPageCitationShareResponseDataItem": ".aeo_page_citation_share_response_data_item",
+ "AeoPageCitationShareResponseFilter": ".aeo_page_citation_share_response_filter",
+ "AeoPageCitationShareResponsePage": ".aeo_page_citation_share_response_page",
+ "AeoPrompt": ".aeo_prompt",
+ "AeoPromptExecution": ".aeo_prompt_execution",
+ "AeoPromptExecutionListResponse": ".aeo_prompt_execution_list_response",
+ "AeoPromptExecutionPromptStatus": ".aeo_prompt_execution_prompt_status",
+ "AeoPromptExecutionStatus": ".aeo_prompt_execution_status",
+ "AeoPromptExecutionTopic": ".aeo_prompt_execution_topic",
+ "AeoPromptIdsFilter": ".aeo_prompt_ids_filter",
+ "AeoPromptLabel": ".aeo_prompt_label",
+ "AeoPromptListResponse": ".aeo_prompt_list_response",
+ "AeoPromptStatus": ".aeo_prompt_status",
+ "AeoPromptTopic": ".aeo_prompt_topic",
+ "AeoTimeseries": ".aeo_timeseries",
+ "AeoTimeseriesGranularityPeriod": ".aeo_timeseries_granularity_period",
+ "AeoTopCitedDomainsResponse": ".aeo_top_cited_domains_response",
+ "AeoTopCitedDomainsResponseDataItem": ".aeo_top_cited_domains_response_data_item",
+ "AeoTopicEngineFilter": ".aeo_topic_engine_filter",
+ "AeoTopicIdsFilter": ".aeo_topic_ids_filter",
+ "AeoTopicLabel": ".aeo_topic_label",
+ "AeoVisibilityBreakdownResponse": ".aeo_visibility_breakdown_response",
+ "AeoVisibilityBreakdownResponseBreakdown": ".aeo_visibility_breakdown_response_breakdown",
+ "AeoVisibilityBreakdownResponseDataItem": ".aeo_visibility_breakdown_response_data_item",
+ "AeoVisibilityFilter": ".aeo_visibility_filter",
+ "AeoVisibilityResponse": ".aeo_visibility_response",
+ "AeoVisibilityResponseDataItem": ".aeo_visibility_response_data_item",
+ "AeoVisibilityScore": ".aeo_visibility_score",
+ "AeoWindow": ".aeo_window",
+ "AnalyzeBucketTimeZone": ".analyze_bucket_time_zone",
+ "AnalyzeDailyBucketing": ".analyze_daily_bucketing",
+ "AnalyzeDailyTimeseriesQuery": ".analyze_daily_timeseries_query",
+ "AnalyzeFilterOperators": ".analyze_filter_operators",
+ "AnalyzeTimeOnPageBucketing": ".analyze_time_on_page_bucketing",
+ "AnalyzeWindow": ".analyze_window",
"Application": ".application",
"Asset": ".asset",
"AssetFolder": ".asset_folder",
@@ -319,8 +625,18 @@
"AuthorizationAuthorizationAuthorizedTo": ".authorization_authorization_authorized_to",
"AuthorizedUser": ".authorized_user",
"BadRequestErrorBody": ".bad_request_error_body",
+ "BranchCreatedWebhook": ".branch_created_webhook",
+ "BranchCreatedWebhookPayload": ".branch_created_webhook_payload",
+ "BranchDeletedWebhook": ".branch_deleted_webhook",
+ "BranchDeletedWebhookPayload": ".branch_deleted_webhook_payload",
+ "BranchMergedWebhook": ".branch_merged_webhook",
+ "BranchMergedWebhookPayload": ".branch_merged_webhook_payload",
+ "BrowserWindowHeightDataPoint": ".browser_window_height_data_point",
+ "BrowserWindowHeightFilter": ".browser_window_height_filter",
+ "BrowserWindowHeightResponse": ".browser_window_height_response",
"BulkCollectionItem": ".bulk_collection_item",
"BulkCollectionItemFieldData": ".bulk_collection_item_field_data",
+ "CollaboratorClientInvite": ".collaborator_client_invite",
"Collection": ".collection",
"CollectionItem": ".collection_item",
"CollectionItemChanged": ".collection_item_changed",
@@ -348,6 +664,7 @@
"CommentPayload": ".comment_payload",
"CommentPayloadAuthor": ".comment_payload_author",
"CommentPayloadMentionedUsersItem": ".comment_payload_mentioned_users_item",
+ "CommentPayloadType": ".comment_payload_type",
"CommentReply": ".comment_reply",
"CommentReplyAuthor": ".comment_reply_author",
"CommentReplyList": ".comment_reply_list",
@@ -366,12 +683,27 @@
"ComponentNode": ".component_node",
"ComponentProperties": ".component_properties",
"ComponentProperty": ".component_property",
- "ComponentPropertyType": ".component_property_type",
+ "ComponentPropertyText": ".component_property_text",
+ "ComponentPropertyTextType": ".component_property_text_type",
"Conflict": ".conflict",
"CustomCodeBlock": ".custom_code_block",
"CustomCodeBlockType": ".custom_code_block_type",
"CustomCodeHostedResponse": ".custom_code_hosted_response",
"CustomCodeInlineResponse": ".custom_code_inline_response",
+ "CustomFont": ".custom_font",
+ "CustomFontAxis": ".custom_font_axis",
+ "CustomFontBatchCreateResponse": ".custom_font_batch_create_response",
+ "CustomFontBatchCreateResponseCreatedItem": ".custom_font_batch_create_response_created_item",
+ "CustomFontBatchCreateResponseFailedItem": ".custom_font_batch_create_response_failed_item",
+ "CustomFontBatchDeleteResponse": ".custom_font_batch_delete_response",
+ "CustomFontBatchDeleteResponseDeletedItem": ".custom_font_batch_delete_response_deleted_item",
+ "CustomFontBatchDeleteResponseFailedItem": ".custom_font_batch_delete_response_failed_item",
+ "CustomFontCreateResponse": ".custom_font_create_response",
+ "CustomFontFontDisplay": ".custom_font_font_display",
+ "CustomFontFormat": ".custom_font_format",
+ "CustomFontUpload": ".custom_font_upload",
+ "CustomFontUploadFields": ".custom_font_upload_fields",
+ "CustomFonts": ".custom_fonts",
"CustomRole": ".custom_role",
"CustomRoleAuditLogItem": ".custom_role_audit_log_item",
"CustomRoleAuditLogItemEventSubType": ".custom_role_audit_log_item_event_sub_type",
@@ -383,10 +715,10 @@
"ErrorCode": ".error_code",
"Field": ".field",
"FieldCreate": ".field_create",
+ "FieldGroup": ".field_group",
"FieldType": ".field_type",
"FieldValidations": ".field_validations",
"FieldValidationsAdditionalProperties": ".field_validations_additional_properties",
- "FieldValidationsAdditionalPropertiesAdditionalProperties": ".field_validations_additional_properties_additional_properties",
"ForbiddenErrorBody": ".forbidden_error_body",
"Form": ".form",
"FormField": ".form_field",
@@ -400,14 +732,47 @@
"FormSubmissionTriggerPayload": ".form_submission_trigger_payload",
"FormSubmissionTriggerPayloadSchemaItem": ".form_submission_trigger_payload_schema_item",
"FormSubmissionTriggerPayloadSchemaItemFieldType": ".form_submission_trigger_payload_schema_item_field_type",
+ "Goal": ".goal",
+ "GoalCategory": ".goal_category",
+ "GoalIntegrationSource": ".goal_integration_source",
+ "GoalPerformanceDataPoint": ".goal_performance_data_point",
+ "GoalPerformanceFilter": ".goal_performance_filter",
+ "GoalPerformanceResponse": ".goal_performance_response",
+ "GoalScope": ".goal_scope",
+ "GoalType": ".goal_type",
+ "GoalValueType": ".goal_value_type",
+ "GoalsResponse": ".goals_response",
+ "GoogleTagId": ".google_tag_id",
+ "GoogleTagIds": ".google_tag_ids",
"ImageNode": ".image_node",
"ImageNodeImage": ".image_node_image",
+ "InsertCollectionItem": ".insert_collection_item",
+ "InsertCollectionItemFieldData": ".insert_collection_item_field_data",
+ "InsertedCollectionItem": ".inserted_collection_item",
+ "InsertedCollectionItemFieldData": ".inserted_collection_item_field_data",
"InvalidDomain": ".invalid_domain",
"InvalidScopes": ".invalid_scopes",
"InventoryItem": ".inventory_item",
"InventoryItemInventoryType": ".inventory_item_inventory_type",
+ "InviteWorkspaceMemberRequestBody": ".invite_workspace_member_request_body",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole": ".invite_workspace_member_request_body_default_site_role_default_site_role",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole": ".invite_workspace_member_request_body_default_site_role_workspace_role",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItem": ".invite_workspace_member_request_body_email_site_access_item",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole": ".invite_workspace_member_request_body_email_site_access_item_site_role",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItem": ".invite_workspace_member_request_body_one_site_access_item",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole": ".invite_workspace_member_request_body_one_site_access_item_site_role",
+ "InviteWorkspaceMemberRequestBodyOneWorkspaceRole": ".invite_workspace_member_request_body_one_workspace_role",
+ "InviteWorkspaceMemberResponseBody": ".invite_workspace_member_response_body",
+ "InviteWorkspaceMemberResponseBodyActive": ".invite_workspace_member_response_body_active",
+ "InviteWorkspaceMemberResponseBody_Active": ".invite_workspace_member_response_body",
+ "InviteWorkspaceMemberResponseBody_Invited": ".invite_workspace_member_response_body",
+ "Invited": ".invited",
+ "ItemsListItemsLiveRequestCreatedOn": ".items_list_items_live_request_created_on",
"ItemsListItemsLiveRequestLastPublished": ".items_list_items_live_request_last_published",
+ "ItemsListItemsLiveRequestLastUpdated": ".items_list_items_live_request_last_updated",
+ "ItemsListItemsRequestCreatedOn": ".items_list_items_request_created_on",
"ItemsListItemsRequestLastPublished": ".items_list_items_request_last_published",
+ "ItemsListItemsRequestLastUpdated": ".items_list_items_request_last_updated",
"ListCustomCodeBlocks": ".list_custom_code_blocks",
"Locale": ".locale",
"Locales": ".locales",
@@ -425,6 +790,46 @@
"Node_TextInput": ".node",
"NotEnterprisePlanSite": ".not_enterprise_plan_site",
"NotEnterprisePlanWorkspace": ".not_enterprise_plan_workspace",
+ "Optimization": ".optimization",
+ "OptimizationAudienceInsightsResponse": ".optimization_audience_insights_response",
+ "OptimizationDimensionValuesResponse": ".optimization_dimension_values_response",
+ "OptimizationDimensionsResponse": ".optimization_dimensions_response",
+ "OptimizationGoalsResponse": ".optimization_goals_response",
+ "OptimizationInterval": ".optimization_interval",
+ "OptimizationListResponse": ".optimization_list_response",
+ "OptimizationStatus": ".optimization_status",
+ "OptimizationSummaryResponse": ".optimization_summary_response",
+ "OptimizationTrendResponse": ".optimization_trend_response",
+ "OptimizationType": ".optimization_type",
+ "OptimizeAppliedFilter": ".optimize_applied_filter",
+ "OptimizeAppliedFilterEntry": ".optimize_applied_filter_entry",
+ "OptimizeAudienceSegment": ".optimize_audience_segment",
+ "OptimizeAudienceVariation": ".optimize_audience_variation",
+ "OptimizeCurrentGoal": ".optimize_current_goal",
+ "OptimizeDimension": ".optimize_dimension",
+ "OptimizeDimensionValue": ".optimize_dimension_value",
+ "OptimizeFilter": ".optimize_filter",
+ "OptimizeFilterOperators": ".optimize_filter_operators",
+ "OptimizeGoal": ".optimize_goal",
+ "OptimizeGoalCountingMethod": ".optimize_goal_counting_method",
+ "OptimizeGoalMetricType": ".optimize_goal_metric_type",
+ "OptimizeGoalScope": ".optimize_goal_scope",
+ "OptimizeGoalSource": ".optimize_goal_source",
+ "OptimizeNonWebflowSiteResource": ".optimize_non_webflow_site_resource",
+ "OptimizeReference": ".optimize_reference",
+ "OptimizeReferenceListResponse": ".optimize_reference_list_response",
+ "OptimizeReferenceResource": ".optimize_reference_resource",
+ "OptimizeReferenceResource_OptimizeSite": ".optimize_reference_resource",
+ "OptimizeReferenceResource_WebflowSite": ".optimize_reference_resource",
+ "OptimizeReportGoal": ".optimize_report_goal",
+ "OptimizeReportIntervalEnd": ".optimize_report_interval_end",
+ "OptimizeReportIntervalStart": ".optimize_report_interval_start",
+ "OptimizeTrendDirection": ".optimize_trend_direction",
+ "OptimizeTrendGranularity": ".optimize_trend_granularity",
+ "OptimizeTrendPoint": ".optimize_trend_point",
+ "OptimizeVariationComparison": ".optimize_variation_comparison",
+ "OptimizeVariationResult": ".optimize_variation_result",
+ "OptimizeWebflowSiteResource": ".optimize_webflow_site_resource",
"OptionField": ".option_field",
"Order": ".order",
"OrderAddress": ".order_address",
@@ -455,6 +860,11 @@
"PageCreatedWebhookPayload": ".page_created_webhook_payload",
"PageDeletedWebhook": ".page_deleted_webhook",
"PageDeletedWebhookPayload": ".page_deleted_webhook_payload",
+ "PageFlowDirection": ".page_flow_direction",
+ "PageFlowFilter": ".page_flow_filter",
+ "PageFlowMetricScope": ".page_flow_metric_scope",
+ "PageFlowResponse": ".page_flow_response",
+ "PageFlowRow": ".page_flow_row",
"PageList": ".page_list",
"PageMetadataUpdatedWebhook": ".page_metadata_updated_webhook",
"PageMetadataUpdatedWebhookPayload": ".page_metadata_updated_webhook_payload",
@@ -464,6 +874,7 @@
"Payload": ".payload",
"PayloadFieldData": ".payload_field_data",
"PaypalDetails": ".paypal_details",
+ "PersonActor": ".person_actor",
"Product": ".product",
"ProductAndSkUs": ".product_and_sk_us",
"ProductAndSkUsList": ".product_and_sk_us_list",
@@ -483,6 +894,9 @@
"ScriptApplyList": ".script_apply_list",
"ScriptApplyLocation": ".script_apply_location",
"Scripts": ".scripts",
+ "ScrollDepthDataPoint": ".scroll_depth_data_point",
+ "ScrollDepthFilter": ".scroll_depth_filter",
+ "ScrollDepthResponse": ".scroll_depth_response",
"SearchButtonNode": ".search_button_node",
"SearchButtonNodeWrite": ".search_button_node_write",
"Select": ".select",
@@ -495,11 +909,19 @@
"SingleLocaleCreatedPayloadFieldData": ".single_locale_created_payload_field_data",
"Site": ".site",
"SiteActivityLogItem": ".site_activity_log_item",
+ "SiteActivityLogItemActorType": ".site_activity_log_item_actor_type",
"SiteActivityLogItemEvent": ".site_activity_log_item_event",
"SiteActivityLogItemResourceOperation": ".site_activity_log_item_resource_operation",
+ "SiteActivityLogItemSource": ".site_activity_log_item_source",
"SiteActivityLogItemUser": ".site_activity_log_item_user",
"SiteActivityLogResponse": ".site_activity_log_response",
"SiteDataCollectionType": ".site_data_collection_type",
+ "SiteMember": ".site_member",
+ "SiteMemberRoles": ".site_member_roles",
+ "SiteMemberRolesSite": ".site_member_roles_site",
+ "SiteMemberRolesWorkspace": ".site_member_roles_workspace",
+ "SiteMembersResponse": ".site_members_response",
+ "SiteMembersResponsePagination": ".site_members_response_pagination",
"SiteMembership": ".site_membership",
"SiteMembershipAuditLogItem": ".site_membership_audit_log_item",
"SiteMembershipAuditLogItemEventSubType": ".site_membership_audit_log_item_event_sub_type",
@@ -508,6 +930,7 @@
"SitePlanName": ".site_plan_name",
"SitePublish": ".site_publish",
"SitePublishPayload": ".site_publish_payload",
+ "SitePublishPayloadPublishScope": ".site_publish_payload_publish_scope",
"Sites": ".sites",
"Sku": ".sku",
"SkuFieldData": ".sku_field_data",
@@ -535,7 +958,36 @@
"TextNode": ".text_node",
"TextNodeText": ".text_node_text",
"TextNodeWrite": ".text_node_write",
+ "TimeOnPageDataPoint": ".time_on_page_data_point",
+ "TimeOnPageFilter": ".time_on_page_filter",
+ "TimeOnPageGranularityPeriod": ".time_on_page_granularity_period",
+ "TimeOnPageMetricScope": ".time_on_page_metric_scope",
+ "TimeOnPageResponse": ".time_on_page_response",
+ "TimeOnPageTimeseriesQuery": ".time_on_page_timeseries_query",
+ "TokenActor": ".token_actor",
+ "TopDimensionsDimension": ".top_dimensions_dimension",
+ "TopDimensionsFilter": ".top_dimensions_filter",
+ "TopDimensionsMetricScope": ".top_dimensions_metric_scope",
+ "TopDimensionsResponse": ".top_dimensions_response",
+ "TopDimensionsRow": ".top_dimensions_row",
+ "TopEventsCmsContextEntry": ".top_events_cms_context_entry",
+ "TopEventsComponentContextEntry": ".top_events_component_context_entry",
+ "TopEventsFilter": ".top_events_filter",
+ "TopEventsResponse": ".top_events_response",
+ "TopEventsRow": ".top_events_row",
+ "TopEventsTimeseriesPoint": ".top_events_timeseries_point",
+ "TopPagesFilter": ".top_pages_filter",
+ "TopPagesResponse": ".top_pages_response",
+ "TopPagesRow": ".top_pages_row",
+ "TopPagesSortBy": ".top_pages_sort_by",
+ "TopPagesTimeseriesPoint": ".top_pages_timeseries_point",
+ "TrafficDataPoint": ".traffic_data_point",
+ "TrafficFilter": ".traffic_filter",
+ "TrafficMetricScope": ".traffic_metric_scope",
+ "TrafficResponse": ".traffic_response",
"TriggerType": ".trigger_type",
+ "UpdateSiteMemberRoleRequestBody": ".update_site_member_role_request_body",
+ "UpdateSiteMemberRoleRequestBodySiteRole": ".update_site_member_role_request_body_site_role",
"UpdatedOrder": ".updated_order",
"UserAccess": ".user_access",
"UserAccessAuditLogItem": ".user_access_audit_log_item",
@@ -543,9 +995,13 @@
"Webhook": ".webhook",
"WebhookFilter": ".webhook_filter",
"WebhookList": ".webhook_list",
+ "WithDefaultSiteRole": ".with_default_site_role",
+ "WithSiteAccess": ".with_site_access",
+ "Workspace": ".workspace",
+ "WorkspaceAuditLogActor": ".workspace_audit_log_actor",
+ "WorkspaceAuditLogActor_Person": ".workspace_audit_log_actor",
+ "WorkspaceAuditLogActor_Token": ".workspace_audit_log_actor",
"WorkspaceAuditLogItem": ".workspace_audit_log_item",
- "WorkspaceAuditLogItemActor": ".workspace_audit_log_item_actor",
- "WorkspaceAuditLogItemPayloadSettingChangeMethod": ".workspace_audit_log_item_payload_setting_change_method",
"WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess": ".workspace_audit_log_item_payload_site_membership_granular_access",
"WorkspaceAuditLogItemPayloadSiteMembershipMethod": ".workspace_audit_log_item_payload_site_membership_method",
"WorkspaceAuditLogItemPayloadSiteMembershipSite": ".workspace_audit_log_item_payload_site_membership_site",
@@ -567,12 +1023,30 @@
"WorkspaceAuditLogItem_WorkspaceMembership": ".workspace_audit_log_item",
"WorkspaceAuditLogItem_WorkspaceSetting": ".workspace_audit_log_item",
"WorkspaceAuditLogResponse": ".workspace_audit_log_response",
+ "WorkspaceBulkRoleChangeResponse": ".workspace_bulk_role_change_response",
+ "WorkspaceBulkRoleChangeResponseResultsItem": ".workspace_bulk_role_change_response_results_item",
+ "WorkspaceBulkRoleChangeResponseResultsItemError": ".workspace_bulk_role_change_response_results_item_error",
+ "WorkspaceBulkRoleChangeResponseResultsItemErrorCode": ".workspace_bulk_role_change_response_results_item_error_code",
+ "WorkspaceBulkRoleChangeResponseResultsItemOutcome": ".workspace_bulk_role_change_response_results_item_outcome",
+ "WorkspaceBulkRoleChangeResponseResultsItemType": ".workspace_bulk_role_change_response_results_item_type",
+ "WorkspaceBulkRoleChangeResponseSummary": ".workspace_bulk_role_change_response_summary",
"WorkspaceInvitation": ".workspace_invitation",
"WorkspaceInvitationAuditLogItem": ".workspace_invitation_audit_log_item",
"WorkspaceInvitationAuditLogItemEventSubType": ".workspace_invitation_audit_log_item_event_sub_type",
+ "WorkspaceInvite": ".workspace_invite",
+ "WorkspaceInviteDefaultSiteRole": ".workspace_invite_default_site_role",
+ "WorkspaceInviteRole": ".workspace_invite_role",
+ "WorkspaceInviteStatus": ".workspace_invite_status",
+ "WorkspaceMember": ".workspace_member",
+ "WorkspaceMemberRoles": ".workspace_member_roles",
+ "WorkspaceMemberRolesSite": ".workspace_member_roles_site",
+ "WorkspaceMemberRolesWorkspace": ".workspace_member_roles_workspace",
+ "WorkspaceMembersResponse": ".workspace_members_response",
+ "WorkspaceMembersResponsePagination": ".workspace_members_response_pagination",
"WorkspaceMembership": ".workspace_membership",
"WorkspaceMembershipAuditLogItem": ".workspace_membership_audit_log_item",
"WorkspaceMembershipAuditLogItemEventSubType": ".workspace_membership_audit_log_item_event_sub_type",
+ "Workspaces": ".workspaces",
}
@@ -598,6 +1072,64 @@ def __dir__():
__all__ = [
+ "AeoAnswerEngine",
+ "AeoAnswerEngineKey",
+ "AeoAnswerEnginesFilter",
+ "AeoBotCategory",
+ "AeoBotProvider",
+ "AeoBotTrafficFilter",
+ "AeoBotTrafficPage",
+ "AeoBotTrafficPagesResponse",
+ "AeoBotTrafficResponse",
+ "AeoBrandNames",
+ "AeoCitationFilter",
+ "AeoCitationRateResponse",
+ "AeoCitationRateResponseBreakdown",
+ "AeoCitationRateResponseDataItem",
+ "AeoCitationsResponse",
+ "AeoCitationsResponseDataItem",
+ "AeoCitedDomainPathsResponse",
+ "AeoCitedPath",
+ "AeoCompetitorCitationsResponse",
+ "AeoCompetitorCitationsResponseDataItem",
+ "AeoOwnedCitedPath",
+ "AeoPage",
+ "AeoPageCitationShareResponse",
+ "AeoPageCitationShareResponseDataItem",
+ "AeoPageCitationShareResponseFilter",
+ "AeoPageCitationShareResponsePage",
+ "AeoPrompt",
+ "AeoPromptExecution",
+ "AeoPromptExecutionListResponse",
+ "AeoPromptExecutionPromptStatus",
+ "AeoPromptExecutionStatus",
+ "AeoPromptExecutionTopic",
+ "AeoPromptIdsFilter",
+ "AeoPromptLabel",
+ "AeoPromptListResponse",
+ "AeoPromptStatus",
+ "AeoPromptTopic",
+ "AeoTimeseries",
+ "AeoTimeseriesGranularityPeriod",
+ "AeoTopCitedDomainsResponse",
+ "AeoTopCitedDomainsResponseDataItem",
+ "AeoTopicEngineFilter",
+ "AeoTopicIdsFilter",
+ "AeoTopicLabel",
+ "AeoVisibilityBreakdownResponse",
+ "AeoVisibilityBreakdownResponseBreakdown",
+ "AeoVisibilityBreakdownResponseDataItem",
+ "AeoVisibilityFilter",
+ "AeoVisibilityResponse",
+ "AeoVisibilityResponseDataItem",
+ "AeoVisibilityScore",
+ "AeoWindow",
+ "AnalyzeBucketTimeZone",
+ "AnalyzeDailyBucketing",
+ "AnalyzeDailyTimeseriesQuery",
+ "AnalyzeFilterOperators",
+ "AnalyzeTimeOnPageBucketing",
+ "AnalyzeWindow",
"Application",
"Asset",
"AssetFolder",
@@ -611,8 +1143,18 @@ def __dir__():
"AuthorizationAuthorizationAuthorizedTo",
"AuthorizedUser",
"BadRequestErrorBody",
+ "BranchCreatedWebhook",
+ "BranchCreatedWebhookPayload",
+ "BranchDeletedWebhook",
+ "BranchDeletedWebhookPayload",
+ "BranchMergedWebhook",
+ "BranchMergedWebhookPayload",
+ "BrowserWindowHeightDataPoint",
+ "BrowserWindowHeightFilter",
+ "BrowserWindowHeightResponse",
"BulkCollectionItem",
"BulkCollectionItemFieldData",
+ "CollaboratorClientInvite",
"Collection",
"CollectionItem",
"CollectionItemChanged",
@@ -640,6 +1182,7 @@ def __dir__():
"CommentPayload",
"CommentPayloadAuthor",
"CommentPayloadMentionedUsersItem",
+ "CommentPayloadType",
"CommentReply",
"CommentReplyAuthor",
"CommentReplyList",
@@ -658,12 +1201,27 @@ def __dir__():
"ComponentNode",
"ComponentProperties",
"ComponentProperty",
- "ComponentPropertyType",
+ "ComponentPropertyText",
+ "ComponentPropertyTextType",
"Conflict",
"CustomCodeBlock",
"CustomCodeBlockType",
"CustomCodeHostedResponse",
"CustomCodeInlineResponse",
+ "CustomFont",
+ "CustomFontAxis",
+ "CustomFontBatchCreateResponse",
+ "CustomFontBatchCreateResponseCreatedItem",
+ "CustomFontBatchCreateResponseFailedItem",
+ "CustomFontBatchDeleteResponse",
+ "CustomFontBatchDeleteResponseDeletedItem",
+ "CustomFontBatchDeleteResponseFailedItem",
+ "CustomFontCreateResponse",
+ "CustomFontFontDisplay",
+ "CustomFontFormat",
+ "CustomFontUpload",
+ "CustomFontUploadFields",
+ "CustomFonts",
"CustomRole",
"CustomRoleAuditLogItem",
"CustomRoleAuditLogItemEventSubType",
@@ -675,10 +1233,10 @@ def __dir__():
"ErrorCode",
"Field",
"FieldCreate",
+ "FieldGroup",
"FieldType",
"FieldValidations",
"FieldValidationsAdditionalProperties",
- "FieldValidationsAdditionalPropertiesAdditionalProperties",
"ForbiddenErrorBody",
"Form",
"FormField",
@@ -692,14 +1250,47 @@ def __dir__():
"FormSubmissionTriggerPayload",
"FormSubmissionTriggerPayloadSchemaItem",
"FormSubmissionTriggerPayloadSchemaItemFieldType",
+ "Goal",
+ "GoalCategory",
+ "GoalIntegrationSource",
+ "GoalPerformanceDataPoint",
+ "GoalPerformanceFilter",
+ "GoalPerformanceResponse",
+ "GoalScope",
+ "GoalType",
+ "GoalValueType",
+ "GoalsResponse",
+ "GoogleTagId",
+ "GoogleTagIds",
"ImageNode",
"ImageNodeImage",
+ "InsertCollectionItem",
+ "InsertCollectionItemFieldData",
+ "InsertedCollectionItem",
+ "InsertedCollectionItemFieldData",
"InvalidDomain",
"InvalidScopes",
"InventoryItem",
"InventoryItemInventoryType",
+ "InviteWorkspaceMemberRequestBody",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole",
+ "InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItem",
+ "InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItem",
+ "InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole",
+ "InviteWorkspaceMemberRequestBodyOneWorkspaceRole",
+ "InviteWorkspaceMemberResponseBody",
+ "InviteWorkspaceMemberResponseBodyActive",
+ "InviteWorkspaceMemberResponseBody_Active",
+ "InviteWorkspaceMemberResponseBody_Invited",
+ "Invited",
+ "ItemsListItemsLiveRequestCreatedOn",
"ItemsListItemsLiveRequestLastPublished",
+ "ItemsListItemsLiveRequestLastUpdated",
+ "ItemsListItemsRequestCreatedOn",
"ItemsListItemsRequestLastPublished",
+ "ItemsListItemsRequestLastUpdated",
"ListCustomCodeBlocks",
"Locale",
"Locales",
@@ -717,6 +1308,46 @@ def __dir__():
"Node_TextInput",
"NotEnterprisePlanSite",
"NotEnterprisePlanWorkspace",
+ "Optimization",
+ "OptimizationAudienceInsightsResponse",
+ "OptimizationDimensionValuesResponse",
+ "OptimizationDimensionsResponse",
+ "OptimizationGoalsResponse",
+ "OptimizationInterval",
+ "OptimizationListResponse",
+ "OptimizationStatus",
+ "OptimizationSummaryResponse",
+ "OptimizationTrendResponse",
+ "OptimizationType",
+ "OptimizeAppliedFilter",
+ "OptimizeAppliedFilterEntry",
+ "OptimizeAudienceSegment",
+ "OptimizeAudienceVariation",
+ "OptimizeCurrentGoal",
+ "OptimizeDimension",
+ "OptimizeDimensionValue",
+ "OptimizeFilter",
+ "OptimizeFilterOperators",
+ "OptimizeGoal",
+ "OptimizeGoalCountingMethod",
+ "OptimizeGoalMetricType",
+ "OptimizeGoalScope",
+ "OptimizeGoalSource",
+ "OptimizeNonWebflowSiteResource",
+ "OptimizeReference",
+ "OptimizeReferenceListResponse",
+ "OptimizeReferenceResource",
+ "OptimizeReferenceResource_OptimizeSite",
+ "OptimizeReferenceResource_WebflowSite",
+ "OptimizeReportGoal",
+ "OptimizeReportIntervalEnd",
+ "OptimizeReportIntervalStart",
+ "OptimizeTrendDirection",
+ "OptimizeTrendGranularity",
+ "OptimizeTrendPoint",
+ "OptimizeVariationComparison",
+ "OptimizeVariationResult",
+ "OptimizeWebflowSiteResource",
"OptionField",
"Order",
"OrderAddress",
@@ -747,6 +1378,11 @@ def __dir__():
"PageCreatedWebhookPayload",
"PageDeletedWebhook",
"PageDeletedWebhookPayload",
+ "PageFlowDirection",
+ "PageFlowFilter",
+ "PageFlowMetricScope",
+ "PageFlowResponse",
+ "PageFlowRow",
"PageList",
"PageMetadataUpdatedWebhook",
"PageMetadataUpdatedWebhookPayload",
@@ -756,6 +1392,7 @@ def __dir__():
"Payload",
"PayloadFieldData",
"PaypalDetails",
+ "PersonActor",
"Product",
"ProductAndSkUs",
"ProductAndSkUsList",
@@ -775,6 +1412,9 @@ def __dir__():
"ScriptApplyList",
"ScriptApplyLocation",
"Scripts",
+ "ScrollDepthDataPoint",
+ "ScrollDepthFilter",
+ "ScrollDepthResponse",
"SearchButtonNode",
"SearchButtonNodeWrite",
"Select",
@@ -787,11 +1427,19 @@ def __dir__():
"SingleLocaleCreatedPayloadFieldData",
"Site",
"SiteActivityLogItem",
+ "SiteActivityLogItemActorType",
"SiteActivityLogItemEvent",
"SiteActivityLogItemResourceOperation",
+ "SiteActivityLogItemSource",
"SiteActivityLogItemUser",
"SiteActivityLogResponse",
"SiteDataCollectionType",
+ "SiteMember",
+ "SiteMemberRoles",
+ "SiteMemberRolesSite",
+ "SiteMemberRolesWorkspace",
+ "SiteMembersResponse",
+ "SiteMembersResponsePagination",
"SiteMembership",
"SiteMembershipAuditLogItem",
"SiteMembershipAuditLogItemEventSubType",
@@ -800,6 +1448,7 @@ def __dir__():
"SitePlanName",
"SitePublish",
"SitePublishPayload",
+ "SitePublishPayloadPublishScope",
"Sites",
"Sku",
"SkuFieldData",
@@ -827,7 +1476,36 @@ def __dir__():
"TextNode",
"TextNodeText",
"TextNodeWrite",
+ "TimeOnPageDataPoint",
+ "TimeOnPageFilter",
+ "TimeOnPageGranularityPeriod",
+ "TimeOnPageMetricScope",
+ "TimeOnPageResponse",
+ "TimeOnPageTimeseriesQuery",
+ "TokenActor",
+ "TopDimensionsDimension",
+ "TopDimensionsFilter",
+ "TopDimensionsMetricScope",
+ "TopDimensionsResponse",
+ "TopDimensionsRow",
+ "TopEventsCmsContextEntry",
+ "TopEventsComponentContextEntry",
+ "TopEventsFilter",
+ "TopEventsResponse",
+ "TopEventsRow",
+ "TopEventsTimeseriesPoint",
+ "TopPagesFilter",
+ "TopPagesResponse",
+ "TopPagesRow",
+ "TopPagesSortBy",
+ "TopPagesTimeseriesPoint",
+ "TrafficDataPoint",
+ "TrafficFilter",
+ "TrafficMetricScope",
+ "TrafficResponse",
"TriggerType",
+ "UpdateSiteMemberRoleRequestBody",
+ "UpdateSiteMemberRoleRequestBodySiteRole",
"UpdatedOrder",
"UserAccess",
"UserAccessAuditLogItem",
@@ -835,9 +1513,13 @@ def __dir__():
"Webhook",
"WebhookFilter",
"WebhookList",
+ "WithDefaultSiteRole",
+ "WithSiteAccess",
+ "Workspace",
+ "WorkspaceAuditLogActor",
+ "WorkspaceAuditLogActor_Person",
+ "WorkspaceAuditLogActor_Token",
"WorkspaceAuditLogItem",
- "WorkspaceAuditLogItemActor",
- "WorkspaceAuditLogItemPayloadSettingChangeMethod",
"WorkspaceAuditLogItemPayloadSiteMembershipGranularAccess",
"WorkspaceAuditLogItemPayloadSiteMembershipMethod",
"WorkspaceAuditLogItemPayloadSiteMembershipSite",
@@ -859,10 +1541,28 @@ def __dir__():
"WorkspaceAuditLogItem_WorkspaceMembership",
"WorkspaceAuditLogItem_WorkspaceSetting",
"WorkspaceAuditLogResponse",
+ "WorkspaceBulkRoleChangeResponse",
+ "WorkspaceBulkRoleChangeResponseResultsItem",
+ "WorkspaceBulkRoleChangeResponseResultsItemError",
+ "WorkspaceBulkRoleChangeResponseResultsItemErrorCode",
+ "WorkspaceBulkRoleChangeResponseResultsItemOutcome",
+ "WorkspaceBulkRoleChangeResponseResultsItemType",
+ "WorkspaceBulkRoleChangeResponseSummary",
"WorkspaceInvitation",
"WorkspaceInvitationAuditLogItem",
"WorkspaceInvitationAuditLogItemEventSubType",
+ "WorkspaceInvite",
+ "WorkspaceInviteDefaultSiteRole",
+ "WorkspaceInviteRole",
+ "WorkspaceInviteStatus",
+ "WorkspaceMember",
+ "WorkspaceMemberRoles",
+ "WorkspaceMemberRolesSite",
+ "WorkspaceMemberRolesWorkspace",
+ "WorkspaceMembersResponse",
+ "WorkspaceMembersResponsePagination",
"WorkspaceMembership",
"WorkspaceMembershipAuditLogItem",
"WorkspaceMembershipAuditLogItemEventSubType",
+ "Workspaces",
]
diff --git a/src/webflow/types/aeo_answer_engine.py b/src/webflow/types/aeo_answer_engine.py
new file mode 100644
index 00000000..684546e7
--- /dev/null
+++ b/src/webflow/types/aeo_answer_engine.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AeoAnswerEngine(UniversalBaseModel):
+ """
+ The answer engine that produced the response.
+ """
+
+ key: str = pydantic.Field()
+ """
+ The answer engine's ID, such as `chat-gpt`, `gemini`, `claude`, or `perplexity`.
+ """
+
+ name: str = pydantic.Field()
+ """
+ The answer engine's display name, such as `ChatGPT`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_answer_engine_key.py b/src/webflow/types/aeo_answer_engine_key.py
new file mode 100644
index 00000000..747f2cc5
--- /dev/null
+++ b/src/webflow/types/aeo_answer_engine_key.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoAnswerEngineKey = typing.Union[typing.Literal["chat-gpt", "gemini", "claude", "perplexity"], typing.Any]
diff --git a/src/webflow/types/aeo_answer_engines_filter.py b/src/webflow/types/aeo_answer_engines_filter.py
new file mode 100644
index 00000000..6df940ff
--- /dev/null
+++ b/src/webflow/types/aeo_answer_engines_filter.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .aeo_answer_engine_key import AeoAnswerEngineKey
+
+AeoAnswerEnginesFilter = typing.List[AeoAnswerEngineKey]
diff --git a/src/webflow/types/aeo_bot_category.py b/src/webflow/types/aeo_bot_category.py
new file mode 100644
index 00000000..40849dca
--- /dev/null
+++ b/src/webflow/types/aeo_bot_category.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoBotCategory = typing.Union[typing.Literal["agent", "indexer", "multiple", "training"], typing.Any]
diff --git a/src/webflow/types/aeo_bot_provider.py b/src/webflow/types/aeo_bot_provider.py
new file mode 100644
index 00000000..aa73e594
--- /dev/null
+++ b/src/webflow/types/aeo_bot_provider.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoBotProvider = typing.Union[
+ typing.Literal[
+ "ai2",
+ "amazon",
+ "anthropic",
+ "apple",
+ "bytedance",
+ "common-crawl",
+ "duckduckgo",
+ "google",
+ "meta",
+ "microsoft",
+ "mistral",
+ "openai",
+ "perplexity",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/aeo_bot_traffic_filter.py b/src/webflow/types/aeo_bot_traffic_filter.py
new file mode 100644
index 00000000..d00925cc
--- /dev/null
+++ b/src/webflow/types/aeo_bot_traffic_filter.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_bot_category import AeoBotCategory
+
+
+class AeoBotTrafficFilter(UniversalBaseModel):
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ bot_providers: typing_extensions.Annotated[
+ typing.Optional[typing.List[str]],
+ FieldMetadata(alias="botProviders"),
+ pydantic.Field(
+ alias="botProviders", description="The bot providers the report was filtered to, in the order sent."
+ ),
+ ] = None
+ bot_category: typing_extensions.Annotated[
+ typing.Optional[AeoBotCategory], FieldMetadata(alias="botCategory"), pydantic.Field(alias="botCategory")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_bot_traffic_page.py b/src/webflow/types/aeo_bot_traffic_page.py
new file mode 100644
index 00000000..3598b6c0
--- /dev/null
+++ b/src/webflow/types/aeo_bot_traffic_page.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_page import AeoPage
+
+
+class AeoBotTrafficPage(UniversalBaseModel):
+ request_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="requestCount"),
+ pydantic.Field(
+ alias="requestCount",
+ description="The number of successful requests that the AI bots Webflow tracks made to this page or file.",
+ ),
+ ]
+ page: typing.Optional[AeoPage] = pydantic.Field(default=None)
+ """
+ The Webflow page or CMS item requested. A file with no page, such as `/sitemap.xml`, has only `title`, set to its path. Omitted when the row has neither a page nor a path.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_bot_traffic_pages_response.py b/src/webflow/types/aeo_bot_traffic_pages_response.py
new file mode 100644
index 00000000..00c70433
--- /dev/null
+++ b/src/webflow/types/aeo_bot_traffic_pages_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .aeo_bot_traffic_filter import AeoBotTrafficFilter
+from .aeo_bot_traffic_page import AeoBotTrafficPage
+from .aeo_window import AeoWindow
+
+
+class AeoBotTrafficPagesResponse(UniversalBaseModel):
+ report: typing.Literal["bot_traffic_pages"] = pydantic.Field(default="bot_traffic_pages")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ data: typing.List[AeoBotTrafficPage] = pydantic.Field()
+ """
+ The pages and files bots requested most, sorted by `requestCount`, highest first.
+ """
+
+ filter: typing.Optional[AeoBotTrafficFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_bot_traffic_response.py b/src/webflow/types/aeo_bot_traffic_response.py
new file mode 100644
index 00000000..15750a0a
--- /dev/null
+++ b/src/webflow/types/aeo_bot_traffic_response.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_bot_traffic_filter import AeoBotTrafficFilter
+from .aeo_window import AeoWindow
+
+
+class AeoBotTrafficResponse(UniversalBaseModel):
+ report: typing.Literal["bot_traffic"] = pydantic.Field(default="bot_traffic")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ total_request_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="totalRequestCount"),
+ pydantic.Field(
+ alias="totalRequestCount",
+ description="The number of successful requests (HTTP `200` or `304`) that the AI bots Webflow tracks made to the site's pages and files.",
+ ),
+ ]
+ filter: typing.Optional[AeoBotTrafficFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_brand_names.py b/src/webflow/types/aeo_brand_names.py
new file mode 100644
index 00000000..6643fcff
--- /dev/null
+++ b/src/webflow/types/aeo_brand_names.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoBrandNames = typing.List[str]
diff --git a/src/webflow/types/aeo_citation_filter.py b/src/webflow/types/aeo_citation_filter.py
new file mode 100644
index 00000000..cf7b95d8
--- /dev/null
+++ b/src/webflow/types/aeo_citation_filter.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engines_filter import AeoAnswerEnginesFilter
+from .aeo_prompt_ids_filter import AeoPromptIdsFilter
+from .aeo_topic_ids_filter import AeoTopicIdsFilter
+
+
+class AeoCitationFilter(UniversalBaseModel):
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ topic_ids: typing_extensions.Annotated[
+ typing.Optional[AeoTopicIdsFilter], FieldMetadata(alias="topicIds"), pydantic.Field(alias="topicIds")
+ ] = None
+ prompt_ids: typing_extensions.Annotated[
+ typing.Optional[AeoPromptIdsFilter], FieldMetadata(alias="promptIds"), pydantic.Field(alias="promptIds")
+ ] = None
+ answer_engines: typing_extensions.Annotated[
+ typing.Optional[AeoAnswerEnginesFilter],
+ FieldMetadata(alias="answerEngines"),
+ pydantic.Field(alias="answerEngines"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_citation_rate_response.py b/src/webflow/types/aeo_citation_rate_response.py
new file mode 100644
index 00000000..0c6eaca9
--- /dev/null
+++ b/src/webflow/types/aeo_citation_rate_response.py
@@ -0,0 +1,44 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .aeo_citation_filter import AeoCitationFilter
+from .aeo_citation_rate_response_breakdown import AeoCitationRateResponseBreakdown
+from .aeo_citation_rate_response_data_item import AeoCitationRateResponseDataItem
+from .aeo_window import AeoWindow
+
+
+class AeoCitationRateResponse(UniversalBaseModel):
+ report: typing.Literal["citation_rate"] = pydantic.Field(default="citation_rate")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ breakdown: AeoCitationRateResponseBreakdown = pydantic.Field()
+ """
+ Whether each row is a topic or a prompt.
+ """
+
+ domains: typing.List[str] = pydantic.Field()
+ """
+ Your domains, which count as citations of your site. These are your Allowed domains and your `webflow.io` subdomain, each with and without `www.`.
+ """
+
+ data: typing.List[AeoCitationRateResponseDataItem] = pydantic.Field()
+ """
+ The topics or prompts, sorted by `citationRate`, highest first.
+ """
+
+ filter: typing.Optional[AeoCitationFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_citation_rate_response_breakdown.py b/src/webflow/types/aeo_citation_rate_response_breakdown.py
new file mode 100644
index 00000000..7a57019b
--- /dev/null
+++ b/src/webflow/types/aeo_citation_rate_response_breakdown.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoCitationRateResponseBreakdown = typing.Union[typing.Literal["topic", "prompt"], typing.Any]
diff --git a/src/webflow/types/aeo_citation_rate_response_data_item.py b/src/webflow/types/aeo_citation_rate_response_data_item.py
new file mode 100644
index 00000000..3927f0b6
--- /dev/null
+++ b/src/webflow/types/aeo_citation_rate_response_data_item.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt_label import AeoPromptLabel
+from .aeo_topic_label import AeoTopicLabel
+
+
+class AeoCitationRateResponseDataItem(UniversalBaseModel):
+ citation_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="citationRate"),
+ pydantic.Field(
+ alias="citationRate",
+ description="Of the responses that cited at least one source, the share that cited one of your domains, from 0 to 1. `0.55` means 55%.",
+ ),
+ ]
+ topic: typing.Optional[AeoTopicLabel] = pydantic.Field(default=None)
+ """
+ The topic. Present when `breakdown` is `topic`.
+ """
+
+ prompt: typing.Optional[AeoPromptLabel] = pydantic.Field(default=None)
+ """
+ The prompt. Present when `breakdown` is `prompt`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_citations_response.py b/src/webflow/types/aeo_citations_response.py
new file mode 100644
index 00000000..2cea5974
--- /dev/null
+++ b/src/webflow/types/aeo_citations_response.py
@@ -0,0 +1,53 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_citation_filter import AeoCitationFilter
+from .aeo_citations_response_data_item import AeoCitationsResponseDataItem
+from .aeo_window import AeoWindow
+
+
+class AeoCitationsResponse(UniversalBaseModel):
+ report: typing.Literal["citations"] = pydantic.Field(default="citations")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ data: typing.List[AeoCitationsResponseDataItem] = pydantic.Field()
+ """
+ The responses, sorted by `timestamp`, newest first. Includes responses that cited nothing.
+ """
+
+ has_more: typing_extensions.Annotated[
+ bool,
+ FieldMetadata(alias="hasMore"),
+ pydantic.Field(
+ alias="hasMore",
+ description="Whether more rows exist after this page. At `limit=1000`, a full page always returns `true`, even when the next page is empty.",
+ ),
+ ]
+ limit: int = pydantic.Field()
+ """
+ The `limit` used.
+ """
+
+ offset: int = pydantic.Field()
+ """
+ The `offset` used.
+ """
+
+ filter: typing.Optional[AeoCitationFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_citations_response_data_item.py b/src/webflow/types/aeo_citations_response_data_item.py
new file mode 100644
index 00000000..2ae69ceb
--- /dev/null
+++ b/src/webflow/types/aeo_citations_response_data_item.py
@@ -0,0 +1,53 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engine import AeoAnswerEngine
+from .aeo_prompt_label import AeoPromptLabel
+from .aeo_topic_label import AeoTopicLabel
+
+
+class AeoCitationsResponseDataItem(UniversalBaseModel):
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ When the response was recorded, in UTC.
+ """
+
+ topic: AeoTopicLabel
+ prompt: AeoPromptLabel
+ answer_engine: typing_extensions.Annotated[
+ AeoAnswerEngine, FieldMetadata(alias="answerEngine"), pydantic.Field(alias="answerEngine")
+ ]
+ citations: typing.List[str] = pydantic.Field()
+ """
+ The URLs the response cited.
+ """
+
+ citation_domains: typing_extensions.Annotated[
+ typing.List[str],
+ FieldMetadata(alias="citationDomains"),
+ pydantic.Field(alias="citationDomains", description="The domains of the cited URLs."),
+ ]
+ citation_paths: typing_extensions.Annotated[
+ typing.List[str],
+ FieldMetadata(alias="citationPaths"),
+ pydantic.Field(alias="citationPaths", description="The paths of the cited URLs."),
+ ]
+ mentions: typing.List[str] = pydantic.Field()
+ """
+ The names found in the response. Get Visibility Score matches this field against your organization name and aliases.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_cited_domain_paths_response.py b/src/webflow/types/aeo_cited_domain_paths_response.py
new file mode 100644
index 00000000..0a59c538
--- /dev/null
+++ b/src/webflow/types/aeo_cited_domain_paths_response.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .aeo_cited_path import AeoCitedPath
+from .aeo_topic_engine_filter import AeoTopicEngineFilter
+from .aeo_window import AeoWindow
+
+
+class AeoCitedDomainPathsResponse(UniversalBaseModel):
+ report: typing.Literal["cited_domain_paths"] = pydantic.Field(default="cited_domain_paths")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ domain: str = pydantic.Field()
+ """
+ The domain, lowercased.
+ """
+
+ data: typing.List[AeoCitedPath] = pydantic.Field()
+ """
+ The cited paths, sorted by `citationRate`, highest first.
+ """
+
+ filter: typing.Optional[AeoTopicEngineFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_cited_path.py b/src/webflow/types/aeo_cited_path.py
new file mode 100644
index 00000000..a8dd9bdf
--- /dev/null
+++ b/src/webflow/types/aeo_cited_path.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AeoCitedPath(UniversalBaseModel):
+ path: str = pydantic.Field()
+ """
+ The cited URL's path and query string, without the host.
+ """
+
+ citation_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="citationRate"),
+ pydantic.Field(alias="citationRate", description="This path's share of all citations, from 0 to 1."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_competitor_citations_response.py b/src/webflow/types/aeo_competitor_citations_response.py
new file mode 100644
index 00000000..d69c24c1
--- /dev/null
+++ b/src/webflow/types/aeo_competitor_citations_response.py
@@ -0,0 +1,43 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_competitor_citations_response_data_item import AeoCompetitorCitationsResponseDataItem
+from .aeo_topic_engine_filter import AeoTopicEngineFilter
+from .aeo_window import AeoWindow
+
+
+class AeoCompetitorCitationsResponse(UniversalBaseModel):
+ report: typing.Literal["competitor_citations"] = pydantic.Field(default="competitor_citations")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ competitor_domains: typing_extensions.Annotated[
+ typing.List[str],
+ FieldMetadata(alias="competitorDomains"),
+ pydantic.Field(
+ alias="competitorDomains",
+ description="Your competitors' domains, from Organization info, each with and without `www.`.",
+ ),
+ ]
+ data: typing.List[AeoCompetitorCitationsResponseDataItem] = pydantic.Field()
+ """
+ The responses that cited a competitor's domain and none of your domains, sorted by `timestamp`, newest first.
+ """
+
+ filter: typing.Optional[AeoTopicEngineFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_competitor_citations_response_data_item.py b/src/webflow/types/aeo_competitor_citations_response_data_item.py
new file mode 100644
index 00000000..b8482678
--- /dev/null
+++ b/src/webflow/types/aeo_competitor_citations_response_data_item.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engine import AeoAnswerEngine
+from .aeo_prompt_label import AeoPromptLabel
+from .aeo_topic_label import AeoTopicLabel
+
+
+class AeoCompetitorCitationsResponseDataItem(UniversalBaseModel):
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ When the response was recorded, in UTC.
+ """
+
+ topic: AeoTopicLabel
+ prompt: AeoPromptLabel
+ answer_engine: typing_extensions.Annotated[
+ AeoAnswerEngine, FieldMetadata(alias="answerEngine"), pydantic.Field(alias="answerEngine")
+ ]
+ citations: typing.List[str] = pydantic.Field()
+ """
+ Every URL the response cited, not only competitor URLs.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_owned_cited_path.py b/src/webflow/types/aeo_owned_cited_path.py
new file mode 100644
index 00000000..1cc1de04
--- /dev/null
+++ b/src/webflow/types/aeo_owned_cited_path.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_page import AeoPage
+
+
+class AeoOwnedCitedPath(UniversalBaseModel):
+ path: str = pydantic.Field()
+ """
+ The cited URL's path and query string, without the host.
+ """
+
+ citation_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="citationRate"),
+ pydantic.Field(alias="citationRate", description="This path's share of all citations, from 0 to 1."),
+ ]
+ page: typing.Optional[AeoPage] = pydantic.Field(default=None)
+ """
+ The Webflow page or CMS item the path maps to. Omitted when none is known.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_page.py b/src/webflow/types/aeo_page.py
new file mode 100644
index 00000000..238ab71d
--- /dev/null
+++ b/src/webflow/types/aeo_page.py
@@ -0,0 +1,43 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AeoPage(UniversalBaseModel):
+ """
+ The Webflow page or CMS item. Each field is present only when known.
+ """
+
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="The page ID. For a CMS item, the ID of its template page."),
+ ] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId", description="The CMS collection ID, for a CMS item."),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(alias="itemSlug", description="The CMS item's slug, for a CMS item."),
+ ] = None
+ title: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The page's title, or the CMS item's name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_page_citation_share_response.py b/src/webflow/types/aeo_page_citation_share_response.py
new file mode 100644
index 00000000..5d250d88
--- /dev/null
+++ b/src/webflow/types/aeo_page_citation_share_response.py
@@ -0,0 +1,47 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .aeo_page_citation_share_response_data_item import AeoPageCitationShareResponseDataItem
+from .aeo_page_citation_share_response_filter import AeoPageCitationShareResponseFilter
+from .aeo_page_citation_share_response_page import AeoPageCitationShareResponsePage
+from .aeo_window import AeoWindow
+
+
+class AeoPageCitationShareResponse(UniversalBaseModel):
+ report: typing.Literal["page_citation_share"] = pydantic.Field(default="page_citation_share")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ page: AeoPageCitationShareResponsePage = pydantic.Field()
+ """
+ The page, as sent. `title` is present only when the page is on this site and its title can be found.
+ """
+
+ breakdown: typing.Optional[typing.Literal["prompt"]] = pydantic.Field(default=None)
+ """
+ Present only when `breakdown` was sent.
+ """
+
+ data: typing.List[AeoPageCitationShareResponseDataItem] = pydantic.Field()
+ """
+ One row for the page, or one row per prompt when `breakdown=prompt`, sorted by `citationShare`, highest first.
+ """
+
+ filter: typing.Optional[AeoPageCitationShareResponseFilter] = pydantic.Field(default=None)
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_page_citation_share_response_data_item.py b/src/webflow/types/aeo_page_citation_share_response_data_item.py
new file mode 100644
index 00000000..feda6dfe
--- /dev/null
+++ b/src/webflow/types/aeo_page_citation_share_response_data_item.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt_label import AeoPromptLabel
+
+
+class AeoPageCitationShareResponseDataItem(UniversalBaseModel):
+ citation_share: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="citationShare"),
+ pydantic.Field(
+ alias="citationShare",
+ description="This page's share of all citations to your domains, from 0 to 1. With `breakdown=prompt`, the prompt's share of this page's citations. `0.18` means 18%.",
+ ),
+ ]
+ prompt: typing.Optional[AeoPromptLabel] = pydantic.Field(default=None)
+ """
+ The prompt. Present when `breakdown` is `prompt`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_page_citation_share_response_filter.py b/src/webflow/types/aeo_page_citation_share_response_filter.py
new file mode 100644
index 00000000..da80ef18
--- /dev/null
+++ b/src/webflow/types/aeo_page_citation_share_response_filter.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engines_filter import AeoAnswerEnginesFilter
+
+
+class AeoPageCitationShareResponseFilter(UniversalBaseModel):
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ answer_engines: typing_extensions.Annotated[
+ typing.Optional[AeoAnswerEnginesFilter],
+ FieldMetadata(alias="answerEngines"),
+ pydantic.Field(alias="answerEngines"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_page_citation_share_response_page.py b/src/webflow/types/aeo_page_citation_share_response_page.py
new file mode 100644
index 00000000..1b647a2b
--- /dev/null
+++ b/src/webflow/types/aeo_page_citation_share_response_page.py
@@ -0,0 +1,41 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AeoPageCitationShareResponsePage(UniversalBaseModel):
+ """
+ The page, as sent. `title` is present only when the page is on this site and its title can be found.
+ """
+
+ page_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId", description="The page ID.")
+ ]
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId", description="The CMS collection ID, for a CMS item."),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(alias="itemSlug", description="The CMS item's slug, for a CMS item."),
+ ] = None
+ title: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The page's title, or the CMS item's name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt.py b/src/webflow/types/aeo_prompt.py
new file mode 100644
index 00000000..c297bd5b
--- /dev/null
+++ b/src/webflow/types/aeo_prompt.py
@@ -0,0 +1,80 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt_status import AeoPromptStatus
+from .aeo_prompt_topic import AeoPromptTopic
+
+
+class AeoPrompt(UniversalBaseModel):
+ """
+ A question you track. Webflow runs each prompt through ChatGPT, Claude, Gemini, and Perplexity once a day.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The prompt ID.
+ """
+
+ text: str = pydantic.Field()
+ """
+ The prompt's text.
+ """
+
+ topic: AeoPromptTopic = pydantic.Field()
+ """
+ The topic the prompt belongs to.
+ """
+
+ language: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The prompt's language, if set.
+ """
+
+ region: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The prompt's region, if set.
+ """
+
+ persona: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The prompt's persona, if set.
+ """
+
+ tags: typing.List[str] = pydantic.Field()
+ """
+ The prompt's tags. Prompts added with Generate with AI have `ai-generated`. Empty when there are none.
+ """
+
+ status: AeoPromptStatus
+ created_at: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="createdAt"),
+ pydantic.Field(alias="createdAt", description="When the prompt was created."),
+ ]
+ updated_at: typing_extensions.Annotated[
+ typing.Optional[dt.datetime],
+ FieldMetadata(alias="updatedAt"),
+ pydantic.Field(
+ alias="updatedAt", description="When the prompt was last updated. Omitted when it hasn't been updated."
+ ),
+ ] = None
+ last_run_at: typing_extensions.Annotated[
+ typing.Optional[dt.datetime],
+ FieldMetadata(alias="lastRunAt"),
+ pydantic.Field(alias="lastRunAt", description="When the prompt last ran. Omitted when it hasn't run yet."),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_execution.py b/src/webflow/types/aeo_prompt_execution.py
new file mode 100644
index 00000000..0cf8e163
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_execution.py
@@ -0,0 +1,99 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engine import AeoAnswerEngine
+from .aeo_prompt_execution_prompt_status import AeoPromptExecutionPromptStatus
+from .aeo_prompt_execution_status import AeoPromptExecutionStatus
+from .aeo_prompt_execution_topic import AeoPromptExecutionTopic
+from .aeo_prompt_label import AeoPromptLabel
+
+
+class AeoPromptExecution(UniversalBaseModel):
+ """
+ One response from one answer engine to one prompt.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The prompt execution ID.
+ """
+
+ prompt: AeoPromptLabel = pydantic.Field()
+ """
+ The prompt that ran. `text` is the prompt's current text, and is omitted when the prompt no longer exists.
+ """
+
+ prompt_status: typing_extensions.Annotated[
+ AeoPromptExecutionPromptStatus,
+ FieldMetadata(alias="promptStatus"),
+ pydantic.Field(
+ alias="promptStatus",
+ description="The prompt's current status, not its status when it ran. One of the prompt statuses, or:\n- `deleted` — The prompt no longer exists.",
+ ),
+ ]
+ topic: typing.Optional[AeoPromptExecutionTopic] = pydantic.Field(default=None)
+ """
+ The topic the prompt belongs to.
+ """
+
+ answer_engine: typing_extensions.Annotated[
+ AeoAnswerEngine, FieldMetadata(alias="answerEngine"), pydantic.Field(alias="answerEngine")
+ ]
+ status: AeoPromptExecutionStatus = pydantic.Field()
+ """
+ Whether the run succeeded.
+ """
+
+ mentions: typing.List[str] = pydantic.Field()
+ """
+ Your organization name and aliases found in the response. Matching is by substring, so "Apple" also matches "Apple Watch."
+ """
+
+ citations: typing.List[str] = pydantic.Field()
+ """
+ The URLs the response cited.
+ """
+
+ tags: typing.List[str] = pydantic.Field()
+ """
+ The prompt's tags when it ran.
+ """
+
+ preview: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The first 500 characters of the response.
+ """
+
+ created_at: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="createdAt"),
+ pydantic.Field(alias="createdAt", description="When the prompt ran."),
+ ]
+ updated_at: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="updatedAt"),
+ pydantic.Field(alias="updatedAt", description="When the execution was last updated."),
+ ]
+ raw_response: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="rawResponse"),
+ pydantic.Field(
+ alias="rawResponse",
+ description="The full response text. Included only while it's still stored, and on List Prompt Executions only with `includeRawResponse=true`.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_execution_list_response.py b/src/webflow/types/aeo_prompt_execution_list_response.py
new file mode 100644
index 00000000..f7834bbf
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_execution_list_response.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt_execution import AeoPromptExecution
+
+
+class AeoPromptExecutionListResponse(UniversalBaseModel):
+ items: typing.List[AeoPromptExecution] = pydantic.Field()
+ """
+ The prompt executions, sorted by `createdAt`, newest first.
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Pass this as `cursor` to get the next page. Present whenever the page is full, so the next page can be empty. Omitted on a page that isn't full.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_execution_prompt_status.py b/src/webflow/types/aeo_prompt_execution_prompt_status.py
new file mode 100644
index 00000000..c4f93541
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_execution_prompt_status.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoPromptExecutionPromptStatus = typing.Union[
+ typing.Literal["active", "paused", "pending", "archived", "deleted"], typing.Any
+]
diff --git a/src/webflow/types/aeo_prompt_execution_status.py b/src/webflow/types/aeo_prompt_execution_status.py
new file mode 100644
index 00000000..4925b94e
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_execution_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoPromptExecutionStatus = typing.Union[typing.Literal["completed", "failed"], typing.Any]
diff --git a/src/webflow/types/aeo_prompt_execution_topic.py b/src/webflow/types/aeo_prompt_execution_topic.py
new file mode 100644
index 00000000..83c4e514
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_execution_topic.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AeoPromptExecutionTopic(UniversalBaseModel):
+ """
+ The topic the prompt belongs to.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The topic ID.
+ """
+
+ name: str = pydantic.Field()
+ """
+ The topic's name. Empty when the topic no longer exists.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_ids_filter.py b/src/webflow/types/aeo_prompt_ids_filter.py
new file mode 100644
index 00000000..e3be25eb
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_ids_filter.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoPromptIdsFilter = typing.List[str]
diff --git a/src/webflow/types/aeo_prompt_label.py b/src/webflow/types/aeo_prompt_label.py
new file mode 100644
index 00000000..1b23c5ac
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_label.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AeoPromptLabel(UniversalBaseModel):
+ """
+ A prompt, which is a question you track.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The prompt ID.
+ """
+
+ text: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The prompt's text. Omitted when the prompt can't be found.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_list_response.py b/src/webflow/types/aeo_prompt_list_response.py
new file mode 100644
index 00000000..2b68cbb6
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_list_response.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt import AeoPrompt
+
+
+class AeoPromptListResponse(UniversalBaseModel):
+ items: typing.List[AeoPrompt] = pydantic.Field()
+ """
+ The prompts, sorted by `createdAt`, newest first.
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Pass this as `cursor` to get the next page. Present whenever the page is full, so the next page can be empty. Omitted on a page that isn't full.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_prompt_status.py b/src/webflow/types/aeo_prompt_status.py
new file mode 100644
index 00000000..0d4ce88b
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoPromptStatus = typing.Union[typing.Literal["active", "paused", "pending", "archived"], typing.Any]
diff --git a/src/webflow/types/aeo_prompt_topic.py b/src/webflow/types/aeo_prompt_topic.py
new file mode 100644
index 00000000..75c17022
--- /dev/null
+++ b/src/webflow/types/aeo_prompt_topic.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AeoPromptTopic(UniversalBaseModel):
+ """
+ The topic the prompt belongs to.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The topic ID.
+ """
+
+ name: str = pydantic.Field()
+ """
+ The topic's name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_timeseries.py b/src/webflow/types/aeo_timeseries.py
new file mode 100644
index 00000000..a44dd50a
--- /dev/null
+++ b/src/webflow/types/aeo_timeseries.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_timeseries_granularity_period import AeoTimeseriesGranularityPeriod
+
+
+class AeoTimeseries(UniversalBaseModel):
+ granularity_period: typing_extensions.Annotated[
+ AeoTimeseriesGranularityPeriod,
+ FieldMetadata(alias="granularityPeriod"),
+ pydantic.Field(
+ alias="granularityPeriod",
+ description="`day` returns one value per day, and `week` returns one value per week, starting on Sunday.",
+ ),
+ ]
+ bucket_time_zone: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="bucketTimeZone"),
+ pydantic.Field(
+ alias="bucketTimeZone",
+ description="The IANA time zone that day and week boundaries are aligned to, such as `UTC` or `America/New_York`.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_timeseries_granularity_period.py b/src/webflow/types/aeo_timeseries_granularity_period.py
new file mode 100644
index 00000000..a4fe887e
--- /dev/null
+++ b/src/webflow/types/aeo_timeseries_granularity_period.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoTimeseriesGranularityPeriod = typing.Union[typing.Literal["day", "week"], typing.Any]
diff --git a/src/webflow/types/aeo_top_cited_domains_response.py b/src/webflow/types/aeo_top_cited_domains_response.py
new file mode 100644
index 00000000..73becd7d
--- /dev/null
+++ b/src/webflow/types/aeo_top_cited_domains_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .aeo_top_cited_domains_response_data_item import AeoTopCitedDomainsResponseDataItem
+from .aeo_topic_engine_filter import AeoTopicEngineFilter
+from .aeo_window import AeoWindow
+
+
+class AeoTopCitedDomainsResponse(UniversalBaseModel):
+ report: typing.Literal["top_cited_domains"] = pydantic.Field(default="top_cited_domains")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ data: typing.List[AeoTopCitedDomainsResponseDataItem] = pydantic.Field()
+ """
+ The most cited domains, sorted by citation count, highest first. Your domains outside the top `limit` are added after the ranked rows.
+ """
+
+ filter: typing.Optional[AeoTopicEngineFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_top_cited_domains_response_data_item.py b/src/webflow/types/aeo_top_cited_domains_response_data_item.py
new file mode 100644
index 00000000..39fe4b57
--- /dev/null
+++ b/src/webflow/types/aeo_top_cited_domains_response_data_item.py
@@ -0,0 +1,46 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_owned_cited_path import AeoOwnedCitedPath
+
+
+class AeoTopCitedDomainsResponseDataItem(UniversalBaseModel):
+ domain: str = pydantic.Field()
+ """
+ The cited domain. `example.com` and `www.example.com` are separate rows.
+ """
+
+ citation_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="citationRate"),
+ pydantic.Field(
+ alias="citationRate", description="This domain's share of all citations, from 0 to 1. `0.4` means 40%."
+ ),
+ ]
+ paths: typing.Optional[typing.List[AeoOwnedCitedPath]] = pydantic.Field(default=None)
+ """
+ Your cited paths on this domain, by citation count, highest first. Present only for your domains.
+ """
+
+ distinct_path_count: typing_extensions.Annotated[
+ typing.Optional[int],
+ FieldMetadata(alias="distinctPathCount"),
+ pydantic.Field(
+ alias="distinctPathCount",
+ description="The approximate number of distinct cited paths on this domain. `0` when only the home page was cited. Present only for domains that aren't yours.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_topic_engine_filter.py b/src/webflow/types/aeo_topic_engine_filter.py
new file mode 100644
index 00000000..7011d0c3
--- /dev/null
+++ b/src/webflow/types/aeo_topic_engine_filter.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engines_filter import AeoAnswerEnginesFilter
+from .aeo_topic_ids_filter import AeoTopicIdsFilter
+
+
+class AeoTopicEngineFilter(UniversalBaseModel):
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ topic_ids: typing_extensions.Annotated[
+ typing.Optional[AeoTopicIdsFilter], FieldMetadata(alias="topicIds"), pydantic.Field(alias="topicIds")
+ ] = None
+ answer_engines: typing_extensions.Annotated[
+ typing.Optional[AeoAnswerEnginesFilter],
+ FieldMetadata(alias="answerEngines"),
+ pydantic.Field(alias="answerEngines"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_topic_ids_filter.py b/src/webflow/types/aeo_topic_ids_filter.py
new file mode 100644
index 00000000..68a7ff48
--- /dev/null
+++ b/src/webflow/types/aeo_topic_ids_filter.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoTopicIdsFilter = typing.List[str]
diff --git a/src/webflow/types/aeo_topic_label.py b/src/webflow/types/aeo_topic_label.py
new file mode 100644
index 00000000..e639800e
--- /dev/null
+++ b/src/webflow/types/aeo_topic_label.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class AeoTopicLabel(UniversalBaseModel):
+ """
+ A topic, which groups related prompts.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The topic ID.
+ """
+
+ name: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The topic's name. Omitted when the topic can't be found.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_breakdown_response.py b/src/webflow/types/aeo_visibility_breakdown_response.py
new file mode 100644
index 00000000..4a130497
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_breakdown_response.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_brand_names import AeoBrandNames
+from .aeo_visibility_breakdown_response_breakdown import AeoVisibilityBreakdownResponseBreakdown
+from .aeo_visibility_breakdown_response_data_item import AeoVisibilityBreakdownResponseDataItem
+from .aeo_visibility_filter import AeoVisibilityFilter
+from .aeo_window import AeoWindow
+
+
+class AeoVisibilityBreakdownResponse(UniversalBaseModel):
+ report: typing.Literal["visibility_breakdown"] = pydantic.Field(default="visibility_breakdown")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ breakdown: AeoVisibilityBreakdownResponseBreakdown = pydantic.Field()
+ """
+ Whether each row is a topic or a prompt.
+ """
+
+ brand_names: typing_extensions.Annotated[
+ AeoBrandNames, FieldMetadata(alias="brandNames"), pydantic.Field(alias="brandNames")
+ ]
+ limit: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ The `limit` sent. Present only when `limit` was sent.
+ """
+
+ data: typing.List[AeoVisibilityBreakdownResponseDataItem] = pydantic.Field()
+ """
+ The topics or prompts, sorted by `visibilityScore`, highest first.
+ """
+
+ filter: typing.Optional[AeoVisibilityFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_breakdown_response_breakdown.py b/src/webflow/types/aeo_visibility_breakdown_response_breakdown.py
new file mode 100644
index 00000000..cb8ed04a
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_breakdown_response_breakdown.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+AeoVisibilityBreakdownResponseBreakdown = typing.Union[typing.Literal["topic", "prompt"], typing.Any]
diff --git a/src/webflow/types/aeo_visibility_breakdown_response_data_item.py b/src/webflow/types/aeo_visibility_breakdown_response_data_item.py
new file mode 100644
index 00000000..bf62ee40
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_breakdown_response_data_item.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_prompt_label import AeoPromptLabel
+from .aeo_topic_label import AeoTopicLabel
+from .aeo_visibility_score import AeoVisibilityScore
+
+
+class AeoVisibilityBreakdownResponseDataItem(UniversalBaseModel):
+ visibility_score: typing_extensions.Annotated[
+ AeoVisibilityScore, FieldMetadata(alias="visibilityScore"), pydantic.Field(alias="visibilityScore")
+ ]
+ topic: typing.Optional[AeoTopicLabel] = pydantic.Field(default=None)
+ """
+ The topic. Present when `breakdown` is `topic`.
+ """
+
+ prompt: typing.Optional[AeoPromptLabel] = pydantic.Field(default=None)
+ """
+ The prompt. Present when `breakdown` is `prompt`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_filter.py b/src/webflow/types/aeo_visibility_filter.py
new file mode 100644
index 00000000..05c2ae2d
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_filter.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_answer_engine_key import AeoAnswerEngineKey
+from .aeo_prompt_ids_filter import AeoPromptIdsFilter
+from .aeo_topic_ids_filter import AeoTopicIdsFilter
+
+
+class AeoVisibilityFilter(UniversalBaseModel):
+ """
+ The filters the report used. Present only when a filter was sent.
+ """
+
+ topic_ids: typing_extensions.Annotated[
+ typing.Optional[AeoTopicIdsFilter], FieldMetadata(alias="topicIds"), pydantic.Field(alias="topicIds")
+ ] = None
+ prompt_ids: typing_extensions.Annotated[
+ typing.Optional[AeoPromptIdsFilter], FieldMetadata(alias="promptIds"), pydantic.Field(alias="promptIds")
+ ] = None
+ answer_engine: typing_extensions.Annotated[
+ typing.Optional[AeoAnswerEngineKey], FieldMetadata(alias="answerEngine"), pydantic.Field(alias="answerEngine")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_response.py b/src/webflow/types/aeo_visibility_response.py
new file mode 100644
index 00000000..7b66d1e0
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_response.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_brand_names import AeoBrandNames
+from .aeo_timeseries import AeoTimeseries
+from .aeo_visibility_filter import AeoVisibilityFilter
+from .aeo_visibility_response_data_item import AeoVisibilityResponseDataItem
+from .aeo_window import AeoWindow
+
+
+class AeoVisibilityResponse(UniversalBaseModel):
+ report: typing.Literal["visibility"] = pydantic.Field(default="visibility")
+ """
+ The report type.
+ """
+
+ window: AeoWindow
+ brand_names: typing_extensions.Annotated[
+ AeoBrandNames, FieldMetadata(alias="brandNames"), pydantic.Field(alias="brandNames")
+ ]
+ bucketing: typing.Optional[AeoTimeseries] = pydantic.Field(default=None)
+ """
+ The `timeseries` settings used. Present only when `timeseries` was sent.
+ """
+
+ data: typing.List[AeoVisibilityResponseDataItem] = pydantic.Field()
+ """
+ One point for the whole time range, or one point per day or week when `timeseries` is sent, oldest first.
+ """
+
+ filter: typing.Optional[AeoVisibilityFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_response_data_item.py b/src/webflow/types/aeo_visibility_response_data_item.py
new file mode 100644
index 00000000..a94a3225
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_response_data_item.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .aeo_visibility_score import AeoVisibilityScore
+
+
+class AeoVisibilityResponseDataItem(UniversalBaseModel):
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ The start of the time range, or the start of the day or week, in UTC.
+ """
+
+ visibility_score: typing_extensions.Annotated[
+ AeoVisibilityScore, FieldMetadata(alias="visibilityScore"), pydantic.Field(alias="visibilityScore")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/aeo_visibility_score.py b/src/webflow/types/aeo_visibility_score.py
new file mode 100644
index 00000000..30b04033
--- /dev/null
+++ b/src/webflow/types/aeo_visibility_score.py
@@ -0,0 +1,3 @@
+# This file was auto-generated by Fern from our API Definition.
+
+AeoVisibilityScore = float
diff --git a/src/webflow/types/aeo_window.py b/src/webflow/types/aeo_window.py
new file mode 100644
index 00000000..6d272d52
--- /dev/null
+++ b/src/webflow/types/aeo_window.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AeoWindow(UniversalBaseModel):
+ """
+ The time range of the report, echoed exactly as sent.
+ """
+
+ start_time: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="startTime"),
+ pydantic.Field(alias="startTime", description="Inclusive start of the time range."),
+ ]
+ end_time: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="endTime"),
+ pydantic.Field(alias="endTime", description="Exclusive end of the time range."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/analyze_bucket_time_zone.py b/src/webflow/types/analyze_bucket_time_zone.py
new file mode 100644
index 00000000..ccd99dc4
--- /dev/null
+++ b/src/webflow/types/analyze_bucket_time_zone.py
@@ -0,0 +1,3 @@
+# This file was auto-generated by Fern from our API Definition.
+
+AnalyzeBucketTimeZone = str
diff --git a/src/webflow/types/analyze_daily_bucketing.py b/src/webflow/types/analyze_daily_bucketing.py
new file mode 100644
index 00000000..b0d88350
--- /dev/null
+++ b/src/webflow/types/analyze_daily_bucketing.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_bucket_time_zone import AnalyzeBucketTimeZone
+
+
+class AnalyzeDailyBucketing(UniversalBaseModel):
+ """
+ Daily bucketing applied to a response.
+ """
+
+ granularity_period: typing_extensions.Annotated[
+ typing.Literal["day"],
+ FieldMetadata(alias="granularityPeriod"),
+ pydantic.Field(alias="granularityPeriod", description="Bucket size used for this response."),
+ ] = "day"
+ bucket_time_zone: typing_extensions.Annotated[
+ AnalyzeBucketTimeZone, FieldMetadata(alias="bucketTimeZone"), pydantic.Field(alias="bucketTimeZone")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/analyze_daily_timeseries_query.py b/src/webflow/types/analyze_daily_timeseries_query.py
new file mode 100644
index 00000000..08f45b12
--- /dev/null
+++ b/src/webflow/types/analyze_daily_timeseries_query.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_bucket_time_zone import AnalyzeBucketTimeZone
+
+
+class AnalyzeDailyTimeseriesQuery(UniversalBaseModel):
+ """
+ Options that opt a report into daily timeseries data.
+ """
+
+ bucket_time_zone: typing_extensions.Annotated[
+ AnalyzeBucketTimeZone, FieldMetadata(alias="bucketTimeZone"), pydantic.Field(alias="bucketTimeZone")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/analyze_filter_operators.py b/src/webflow/types/analyze_filter_operators.py
new file mode 100644
index 00000000..8646a0fc
--- /dev/null
+++ b/src/webflow/types/analyze_filter_operators.py
@@ -0,0 +1,46 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AnalyzeFilterOperators(UniversalBaseModel):
+ """
+ Operators for filtering a single dimension. Specify at least one of `eq`, `in`, `ne`, or `nin`.
+ """
+
+ eq: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Match values exactly equal to the provided value.
+ """
+
+ in_: typing_extensions.Annotated[
+ typing.Optional[typing.List[str]],
+ FieldMetadata(alias="in"),
+ pydantic.Field(
+ alias="in",
+ description="Match values in the provided list. Use indexed bracket notation — `filter[][in][0]=value1&filter[][in][1]=value2`. Comma-separated values (for example, `filter[][in]=value1,value2`) are not supported.",
+ ),
+ ] = None
+ ne: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Exclude values exactly equal to the provided value.
+ """
+
+ nin: typing.Optional[typing.List[str]] = pydantic.Field(default=None)
+ """
+ Exclude values in the provided list. Use indexed bracket notation — `filter[][nin][0]=value1&filter[][nin][1]=value2`. Comma-separated values (for example, `filter[][nin]=value1,value2`) are not supported.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/analyze_time_on_page_bucketing.py b/src/webflow/types/analyze_time_on_page_bucketing.py
new file mode 100644
index 00000000..953ec221
--- /dev/null
+++ b/src/webflow/types/analyze_time_on_page_bucketing.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .time_on_page_granularity_period import TimeOnPageGranularityPeriod
+
+
+class AnalyzeTimeOnPageBucketing(UniversalBaseModel):
+ """
+ Bucketing applied to a time on page response.
+ """
+
+ granularity_period: typing_extensions.Annotated[
+ TimeOnPageGranularityPeriod, FieldMetadata(alias="granularityPeriod"), pydantic.Field(alias="granularityPeriod")
+ ]
+ bucket_time_zone: typing_extensions.Annotated[
+ AnalyzeBucketTimeZone, FieldMetadata(alias="bucketTimeZone"), pydantic.Field(alias="bucketTimeZone")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/analyze_window.py b/src/webflow/types/analyze_window.py
new file mode 100644
index 00000000..657c94ca
--- /dev/null
+++ b/src/webflow/types/analyze_window.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class AnalyzeWindow(UniversalBaseModel):
+ """
+ A reporting time window. `endTime` must be greater than `startTime`.
+ """
+
+ start_time: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="startTime"),
+ pydantic.Field(
+ alias="startTime", description="Inclusive start of the reporting window, in ISO 8601 / RFC 3339 format."
+ ),
+ ]
+ end_time: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="endTime"),
+ pydantic.Field(
+ alias="endTime", description="Exclusive end of the reporting window, in ISO 8601 / RFC 3339 format."
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/asset.py b/src/webflow/types/asset.py
index eb3425dd..4921b373 100644
--- a/src/webflow/types/asset.py
+++ b/src/webflow/types/asset.py
@@ -70,6 +70,14 @@ class Asset(UniversalBaseModel):
FieldMetadata(alias="altText"),
pydantic.Field(alias="altText", description="The visual description of the asset"),
] = None
+ folder_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="folderId"),
+ pydantic.Field(
+ alias="folderId",
+ description="The ID of the folder the asset belongs to, or `null` if the asset is at the site root.\nThis field is present only in list responses (`GET /sites/{site_id}/assets`).",
+ ),
+ ] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/authorization.py b/src/webflow/types/authorization.py
index 6deea56d..ee0673f9 100644
--- a/src/webflow/types/authorization.py
+++ b/src/webflow/types/authorization.py
@@ -14,7 +14,10 @@ class Authorization(UniversalBaseModel):
The Authorization object
"""
- application: typing.Optional[Application] = None
+ application: typing.Optional[Application] = pydantic.Field(default=None)
+ """
+ Details of the OAuth app the token belongs to. Present only for tokens issued to an OAuth app. Omitted for `client_credentials` tokens without an OAuth app, such as Site and Workspace API tokens.
+ """
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/branch_created_webhook.py b/src/webflow/types/branch_created_webhook.py
new file mode 100644
index 00000000..7644f318
--- /dev/null
+++ b/src/webflow/types/branch_created_webhook.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .branch_created_webhook_payload import BranchCreatedWebhookPayload
+
+
+class BranchCreatedWebhook(UniversalBaseModel):
+ """
+ The Webhook payload for when a branch is created
+ """
+
+ trigger_type: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="triggerType"), pydantic.Field(alias="triggerType")
+ ] = None
+ payload: typing.Optional[BranchCreatedWebhookPayload] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/branch_created_webhook_payload.py b/src/webflow/types/branch_created_webhook_payload.py
new file mode 100644
index 00000000..9aa43a92
--- /dev/null
+++ b/src/webflow/types/branch_created_webhook_payload.py
@@ -0,0 +1,49 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class BranchCreatedWebhookPayload(UniversalBaseModel):
+ site_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")
+ ] = None
+ branch_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="branchId"), pydantic.Field(alias="branchId")
+ ] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="ID of the branch page (the duplicate page)."),
+ ] = None
+ parent_page_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="parentPageId"),
+ pydantic.Field(
+ alias="parentPageId",
+ description="ID of the original page branched from, or an empty string for standalone branches.",
+ ),
+ ] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="pageName"), pydantic.Field(alias="pageName")
+ ] = None
+ created_by: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="createdBy"), pydantic.Field(alias="createdBy")
+ ] = None
+ created_on: typing_extensions.Annotated[
+ typing.Optional[dt.datetime], FieldMetadata(alias="createdOn"), pydantic.Field(alias="createdOn")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/branch_deleted_webhook.py b/src/webflow/types/branch_deleted_webhook.py
new file mode 100644
index 00000000..9ba12f20
--- /dev/null
+++ b/src/webflow/types/branch_deleted_webhook.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .branch_deleted_webhook_payload import BranchDeletedWebhookPayload
+
+
+class BranchDeletedWebhook(UniversalBaseModel):
+ """
+ The Webhook payload for when a branch is explicitly deleted
+ """
+
+ trigger_type: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="triggerType"), pydantic.Field(alias="triggerType")
+ ] = None
+ payload: typing.Optional[BranchDeletedWebhookPayload] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/branch_deleted_webhook_payload.py b/src/webflow/types/branch_deleted_webhook_payload.py
new file mode 100644
index 00000000..d731aee2
--- /dev/null
+++ b/src/webflow/types/branch_deleted_webhook_payload.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class BranchDeletedWebhookPayload(UniversalBaseModel):
+ site_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")
+ ] = None
+ branch_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="branchId"), pydantic.Field(alias="branchId")
+ ] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ parent_page_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="parentPageId"), pydantic.Field(alias="parentPageId")
+ ] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="pageName"), pydantic.Field(alias="pageName")
+ ] = None
+ deleted_by: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="deletedBy"), pydantic.Field(alias="deletedBy")
+ ] = None
+ deleted_on: typing_extensions.Annotated[
+ typing.Optional[dt.datetime], FieldMetadata(alias="deletedOn"), pydantic.Field(alias="deletedOn")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/branch_merged_webhook.py b/src/webflow/types/branch_merged_webhook.py
new file mode 100644
index 00000000..28020ae6
--- /dev/null
+++ b/src/webflow/types/branch_merged_webhook.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .branch_merged_webhook_payload import BranchMergedWebhookPayload
+
+
+class BranchMergedWebhook(UniversalBaseModel):
+ """
+ The Webhook payload for when a branch is merged into main
+ """
+
+ trigger_type: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="triggerType"), pydantic.Field(alias="triggerType")
+ ] = None
+ payload: typing.Optional[BranchMergedWebhookPayload] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/branch_merged_webhook_payload.py b/src/webflow/types/branch_merged_webhook_payload.py
new file mode 100644
index 00000000..54fc9852
--- /dev/null
+++ b/src/webflow/types/branch_merged_webhook_payload.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class BranchMergedWebhookPayload(UniversalBaseModel):
+ site_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")
+ ] = None
+ branch_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="branchId"), pydantic.Field(alias="branchId")
+ ] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ parent_page_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="parentPageId"), pydantic.Field(alias="parentPageId")
+ ] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="pageName"), pydantic.Field(alias="pageName")
+ ] = None
+ merged_by: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="mergedBy"), pydantic.Field(alias="mergedBy")
+ ] = None
+ merged_on: typing_extensions.Annotated[
+ typing.Optional[dt.datetime], FieldMetadata(alias="mergedOn"), pydantic.Field(alias="mergedOn")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/browser_window_height_data_point.py b/src/webflow/types/browser_window_height_data_point.py
new file mode 100644
index 00000000..23201b44
--- /dev/null
+++ b/src/webflow/types/browser_window_height_data_point.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class BrowserWindowHeightDataPoint(UniversalBaseModel):
+ """
+ A single average browser window height data point. Wrapped in a one-element `data` array for SDK uniformity with other Analyze reports.
+ """
+
+ average_browser_window_height_pixels: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="averageBrowserWindowHeightPixels"),
+ pydantic.Field(
+ alias="averageBrowserWindowHeightPixels",
+ description="Average browser window height over the requested window, in CSS pixels.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/browser_window_height_filter.py b/src/webflow/types/browser_window_height_filter.py
new file mode 100644
index 00000000..c11aef6c
--- /dev/null
+++ b/src/webflow/types/browser_window_height_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class BrowserWindowHeightFilter(UniversalBaseModel):
+ """
+ Filter the browser window height report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/browser_window_height_response.py b/src/webflow/types/browser_window_height_response.py
new file mode 100644
index 00000000..dde1b87d
--- /dev/null
+++ b/src/webflow/types/browser_window_height_response.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .analyze_window import AnalyzeWindow
+from .browser_window_height_data_point import BrowserWindowHeightDataPoint
+from .browser_window_height_filter import BrowserWindowHeightFilter
+
+
+class BrowserWindowHeightResponse(UniversalBaseModel):
+ """
+ Response payload for the browser window height report.
+ """
+
+ report: typing.Literal["browser_window_height"] = pydantic.Field(default="browser_window_height")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ data: typing.List[BrowserWindowHeightDataPoint] = pydantic.Field()
+ """
+ Average browser window height for the window. Typically contains one row, but may be empty when upstream data is sparse.
+ """
+
+ filter: typing.Optional[BrowserWindowHeightFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/bulk_collection_item.py b/src/webflow/types/bulk_collection_item.py
index f53e06d1..394fbd57 100644
--- a/src/webflow/types/bulk_collection_item.py
+++ b/src/webflow/types/bulk_collection_item.py
@@ -49,7 +49,10 @@ class BulkCollectionItem(UniversalBaseModel):
is_draft: typing_extensions.Annotated[
typing.Optional[bool],
FieldMetadata(alias="isDraft"),
- pydantic.Field(alias="isDraft", description="Boolean determining if the Item is set to draft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Whether the item is in a draft state. `true` unless the request set `isDraft: false`.",
+ ),
] = None
field_data: typing_extensions.Annotated[
typing.Optional[BulkCollectionItemFieldData],
diff --git a/src/webflow/types/collaborator_client_invite.py b/src/webflow/types/collaborator_client_invite.py
new file mode 100644
index 00000000..ac987e9d
--- /dev/null
+++ b/src/webflow/types/collaborator_client_invite.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .invite_workspace_member_request_body_email_site_access_item import (
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItem,
+)
+
+
+class CollaboratorClientInvite(UniversalBaseModel):
+ email: str
+ workspace_role: typing_extensions.Annotated[
+ typing.Literal["client"],
+ FieldMetadata(alias="workspaceRole"),
+ pydantic.Field(alias="workspaceRole", description="The Workspace Role to invite the person as"),
+ ] = "client"
+ site_access: typing_extensions.Annotated[
+ typing.List[InviteWorkspaceMemberRequestBodyEmailSiteAccessItem],
+ FieldMetadata(alias="siteAccess"),
+ pydantic.Field(alias="siteAccess", description="The one Site where the collaborator client receives access."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/collection.py b/src/webflow/types/collection.py
index 1976f760..0c94c60a 100644
--- a/src/webflow/types/collection.py
+++ b/src/webflow/types/collection.py
@@ -8,6 +8,7 @@
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ..core.serialization import FieldMetadata
from .field import Field
+from .field_group import FieldGroup
class Collection(UniversalBaseModel):
@@ -53,6 +54,12 @@ class Collection(UniversalBaseModel):
The list of fields in the Collection
"""
+ field_groups: typing_extensions.Annotated[
+ typing.Optional[typing.List[FieldGroup]],
+ FieldMetadata(alias="fieldGroups"),
+ pydantic.Field(alias="fieldGroups", description="The list of field groups in the Collection"),
+ ] = None
+
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
else:
diff --git a/src/webflow/types/collection_item.py b/src/webflow/types/collection_item.py
index 832a11cb..f20f9471 100644
--- a/src/webflow/types/collection_item.py
+++ b/src/webflow/types/collection_item.py
@@ -51,7 +51,10 @@ class CollectionItem(UniversalBaseModel):
is_draft: typing_extensions.Annotated[
typing.Optional[bool],
FieldMetadata(alias="isDraft"),
- pydantic.Field(alias="isDraft", description="Boolean determining if the Item is set to draft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Whether the item is in a draft state. Together with `lastPublished`, this determines the status shown in the Webflow UI. See [Publishing with the CMS API](/data/docs/working-with-the-cms/publishing) for the full mapping.",
+ ),
] = None
field_data: typing_extensions.Annotated[
CollectionItemFieldData, FieldMetadata(alias="fieldData"), pydantic.Field(alias="fieldData")
diff --git a/src/webflow/types/collection_item_patch_single.py b/src/webflow/types/collection_item_patch_single.py
index 64b01707..dc81b834 100644
--- a/src/webflow/types/collection_item_patch_single.py
+++ b/src/webflow/types/collection_item_patch_single.py
@@ -47,7 +47,10 @@ class CollectionItemPatchSingle(UniversalBaseModel):
is_draft: typing_extensions.Annotated[
typing.Optional[bool],
FieldMetadata(alias="isDraft"),
- pydantic.Field(alias="isDraft", description="Boolean determining if the Item is set to draft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Sets the item's draft state. The resulting status depends on whether the item has been published before:\n\n- **Item that has never been published:** `isDraft: true` results in a `Draft` status.\n- **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.\n\nSetting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).",
+ ),
] = None
field_data: typing_extensions.Annotated[
typing.Optional[CollectionItemPatchSingleFieldData],
diff --git a/src/webflow/types/collection_item_post_single.py b/src/webflow/types/collection_item_post_single.py
index f58465ad..25268d52 100644
--- a/src/webflow/types/collection_item_post_single.py
+++ b/src/webflow/types/collection_item_post_single.py
@@ -51,7 +51,10 @@ class CollectionItemPostSingle(UniversalBaseModel):
is_draft: typing_extensions.Annotated[
typing.Optional[bool],
FieldMetadata(alias="isDraft"),
- pydantic.Field(alias="isDraft", description="Boolean determining if the Item is in a draft state."),
+ pydantic.Field(
+ alias="isDraft",
+ description="Whether the item is created in a draft state. A new item has never been published, so `isDraft: true` gives it a `Draft` status. Set `isDraft: false` to queue the item to publish on the next site publish.",
+ ),
] = None
field_data: typing_extensions.Annotated[
CollectionItemPostSingleFieldData, FieldMetadata(alias="fieldData"), pydantic.Field(alias="fieldData")
diff --git a/src/webflow/types/collection_item_with_id_input.py b/src/webflow/types/collection_item_with_id_input.py
index 0a1aae28..7eb35484 100644
--- a/src/webflow/types/collection_item_with_id_input.py
+++ b/src/webflow/types/collection_item_with_id_input.py
@@ -47,7 +47,10 @@ class CollectionItemWithIdInput(UniversalBaseModel):
is_draft: typing_extensions.Annotated[
typing.Optional[bool],
FieldMetadata(alias="isDraft"),
- pydantic.Field(alias="isDraft", description="Boolean determining if the Item is set to draft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Sets the item's draft state. The resulting status depends on whether the item has been published before:\n\n- **Item that has never been published:** `isDraft: true` results in a `Draft` status.\n- **Already-published item:** `isDraft: true` results in a `Changes in draft` status. The live item stays published, and your changes are held back until you publish them.\n\nSetting `isDraft: true` never unpublishes an item. To remove an item from the live site, use [Unpublish Live Collection Items](/data/reference/cms/collection-items/live-items/delete-items-live).",
+ ),
] = None
field_data: typing_extensions.Annotated[
typing.Optional[CollectionItemWithIdInputFieldData],
diff --git a/src/webflow/types/comment_payload.py b/src/webflow/types/comment_payload.py
index 8b51391f..8ab4b03d 100644
--- a/src/webflow/types/comment_payload.py
+++ b/src/webflow/types/comment_payload.py
@@ -8,6 +8,7 @@
from ..core.serialization import FieldMetadata
from .comment_payload_author import CommentPayloadAuthor
from .comment_payload_mentioned_users_item import CommentPayloadMentionedUsersItem
+from .comment_payload_type import CommentPayloadType
class CommentPayload(UniversalBaseModel):
@@ -25,9 +26,9 @@ class CommentPayload(UniversalBaseModel):
FieldMetadata(alias="commentId"),
pydantic.Field(alias="commentId", description="Unique identifier for the comment reply"),
] = None
- type: typing.Optional[str] = pydantic.Field(default=None)
+ type: typing.Optional[CommentPayloadType] = pydantic.Field(default=None)
"""
- The type of comment payload
+ The type of comment payload. `new_comment` indicates a new thread; `reply` indicates a reply to an existing thread.
"""
site_id: typing_extensions.Annotated[
@@ -38,18 +39,15 @@ class CommentPayload(UniversalBaseModel):
page_id: typing_extensions.Annotated[
typing.Optional[str],
FieldMetadata(alias="pageId"),
- pydantic.Field(alias="pageId", description="The page unique identifier"),
+ pydantic.Field(
+ alias="pageId", description="The page unique identifier, or for CMS item comments, the template page ID"
+ ),
] = None
locale_id: typing_extensions.Annotated[
typing.Optional[str],
FieldMetadata(alias="localeId"),
pydantic.Field(alias="localeId", description="The locale unique identifier"),
] = None
- item_id: typing_extensions.Annotated[
- typing.Optional[str],
- FieldMetadata(alias="itemId"),
- pydantic.Field(alias="itemId", description="The item unique identifier"),
- ] = None
breakpoint: typing.Optional[str] = pydantic.Field(default=None)
"""
The breakpoint the comment was left on
diff --git a/src/webflow/types/comment_payload_type.py b/src/webflow/types/comment_payload_type.py
new file mode 100644
index 00000000..27e5c605
--- /dev/null
+++ b/src/webflow/types/comment_payload_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CommentPayloadType = typing.Union[typing.Literal["new_comment", "reply"], typing.Any]
diff --git a/src/webflow/types/component_property.py b/src/webflow/types/component_property.py
index 4583f4da..fd30a17c 100644
--- a/src/webflow/types/component_property.py
+++ b/src/webflow/types/component_property.py
@@ -1,45 +1,5 @@
# This file was auto-generated by Fern from our API Definition.
-import typing
+from .component_property_text import ComponentPropertyText
-import pydantic
-import typing_extensions
-from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
-from ..core.serialization import FieldMetadata
-from .component_property_type import ComponentPropertyType
-from .text import Text
-
-
-class ComponentProperty(UniversalBaseModel):
- """
- Represents a property of a component instance in the DOM. A property contains a list of both the raw text and the HTML representation, allowing for flexibility in rendering and processing. Additional attributes can be associated with the text for styling or other purposes.
- """
-
- property_id: typing_extensions.Annotated[
- typing.Optional[str],
- FieldMetadata(alias="propertyId"),
- pydantic.Field(alias="propertyId", description="The ID of the property."),
- ] = None
- type: typing.Optional[ComponentPropertyType] = pydantic.Field(default=None)
- """
- The type of the property.
- """
-
- label: typing.Optional[str] = pydantic.Field(default=None)
- """
- The label of the property in the UI.
- """
-
- text: typing.Optional[Text] = pydantic.Field(default=None)
- """
- Represents text content within the DOM. It contains both the raw text and its HTML representation.
- """
-
- if IS_PYDANTIC_V2:
- model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
- else:
-
- class Config:
- frozen = True
- smart_union = True
- extra = pydantic.Extra.allow
+ComponentProperty = ComponentPropertyText
diff --git a/src/webflow/types/component_property_text.py b/src/webflow/types/component_property_text.py
new file mode 100644
index 00000000..c7085519
--- /dev/null
+++ b/src/webflow/types/component_property_text.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .component_property_text_type import ComponentPropertyTextType
+from .text import Text
+
+
+class ComponentPropertyText(UniversalBaseModel):
+ """
+ A text-based component property containing raw text and HTML representation.
+ """
+
+ property_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="propertyId"),
+ pydantic.Field(alias="propertyId", description="The ID of the property."),
+ ]
+ type: ComponentPropertyTextType = pydantic.Field()
+ """
+ The type of the property.
+ """
+
+ label: str = pydantic.Field()
+ """
+ The label of the property in the UI.
+ """
+
+ text: Text = pydantic.Field()
+ """
+ Represents text content within the DOM. It contains both the raw text and its HTML representation.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/component_property_text_type.py b/src/webflow/types/component_property_text_type.py
new file mode 100644
index 00000000..6038e5ca
--- /dev/null
+++ b/src/webflow/types/component_property_text_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ComponentPropertyTextType = typing.Union[typing.Literal["Plain Text", "Rich Text", "Alt Text"], typing.Any]
diff --git a/src/webflow/types/component_property_type.py b/src/webflow/types/component_property_type.py
deleted file mode 100644
index 117b2fe6..00000000
--- a/src/webflow/types/component_property_type.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# This file was auto-generated by Fern from our API Definition.
-
-import typing
-
-ComponentPropertyType = typing.Union[typing.Literal["Plain Text", "Rich Text", "Alt Text"], typing.Any]
diff --git a/src/webflow/types/custom_font.py b/src/webflow/types/custom_font.py
new file mode 100644
index 00000000..598edc3d
--- /dev/null
+++ b/src/webflow/types/custom_font.py
@@ -0,0 +1,72 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .custom_font_axis import CustomFontAxis
+from .custom_font_font_display import CustomFontFontDisplay
+from .custom_font_format import CustomFontFormat
+
+
+class CustomFont(UniversalBaseModel):
+ """
+ A custom font uploaded to a Webflow site
+ """
+
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Unique identifier for the custom font
+ """
+
+ font_family: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="fontFamily"),
+ pydantic.Field(alias="fontFamily", description="The CSS font-family name. Commas are stripped server-side."),
+ ]
+ format: typing.Optional[CustomFontFormat] = pydantic.Field(default=None)
+ """
+ The font file format, derived from the file extension. The value `svg` represents read-only legacy data; new SVG font uploads are not accepted.
+ """
+
+ file_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="fileName"),
+ pydantic.Field(alias="fileName", description="The file name supplied at upload time"),
+ ] = None
+ weight: int = pydantic.Field()
+ """
+ The CSS font-weight value (1–1000)
+ """
+
+ italic: bool = pydantic.Field()
+ """
+ Whether the font is italic
+ """
+
+ font_display: typing_extensions.Annotated[
+ CustomFontFontDisplay,
+ FieldMetadata(alias="fontDisplay"),
+ pydantic.Field(alias="fontDisplay", description="The CSS font-display value"),
+ ]
+ axes: typing.List[CustomFontAxis] = pydantic.Field()
+ """
+ Variable font axes. An empty array indicates a static font.
+ """
+
+ hosted_url: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="hostedUrl"),
+ pydantic.Field(alias="hostedUrl", description="CDN URL for the font binary"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_axis.py b/src/webflow/types/custom_font_axis.py
new file mode 100644
index 00000000..297084d7
--- /dev/null
+++ b/src/webflow/types/custom_font_axis.py
@@ -0,0 +1,52 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class CustomFontAxis(UniversalBaseModel):
+ """
+ A variable font axis definition
+ """
+
+ tag: str = pydantic.Field()
+ """
+ The four-character OpenType axis tag, consisting of exactly 4 printable ASCII characters (for example, `wght`, `opsz`, `slnt`, `wdth`)
+ """
+
+ name: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Optional human-readable label for the axis
+ """
+
+ min: float = pydantic.Field()
+ """
+ Minimum value for the axis
+ """
+
+ max: float = pydantic.Field()
+ """
+ Maximum value for the axis
+ """
+
+ default_value: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="defaultValue"),
+ pydantic.Field(
+ alias="defaultValue",
+ description="Default value for the axis. Must be between `min` and `max` (inclusive). Violating this constraint returns `400 BadArgument` with an error prefixed `fontMetadata.axes`.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_batch_create_response.py b/src/webflow/types/custom_font_batch_create_response.py
new file mode 100644
index 00000000..fe7c0108
--- /dev/null
+++ b/src/webflow/types/custom_font_batch_create_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .custom_font_batch_create_response_created_item import CustomFontBatchCreateResponseCreatedItem
+from .custom_font_batch_create_response_failed_item import CustomFontBatchCreateResponseFailedItem
+
+
+class CustomFontBatchCreateResponse(UniversalBaseModel):
+ """
+ Per-item result of a bulk-create operation
+ """
+
+ created: typing.List[CustomFontBatchCreateResponseCreatedItem] = pydantic.Field()
+ """
+ Successfully registered fonts, each with its own presigned S3 upload details.
+ """
+
+ failed: typing.List[CustomFontBatchCreateResponseFailedItem] = pydantic.Field()
+ """
+ Requested fonts that could not be registered. Valid fonts elsewhere in the batch are still registered.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/field_validations_additional_properties_additional_properties.py b/src/webflow/types/custom_font_batch_create_response_created_item.py
similarity index 63%
rename from src/webflow/types/field_validations_additional_properties_additional_properties.py
rename to src/webflow/types/custom_font_batch_create_response_created_item.py
index 0c676dd1..8f6e546d 100644
--- a/src/webflow/types/field_validations_additional_properties_additional_properties.py
+++ b/src/webflow/types/custom_font_batch_create_response_created_item.py
@@ -6,12 +6,15 @@
import typing_extensions
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ..core.serialization import FieldMetadata
+from .custom_font import CustomFont
+from .custom_font_upload import CustomFontUpload
-class FieldValidationsAdditionalPropertiesAdditionalProperties(UniversalBaseModel):
- additional_properties: typing_extensions.Annotated[
- typing.Any, FieldMetadata(alias="additionalProperties"), pydantic.Field(alias="additionalProperties")
+class CustomFontBatchCreateResponseCreatedItem(UniversalBaseModel):
+ custom_font: typing_extensions.Annotated[
+ CustomFont, FieldMetadata(alias="customFont"), pydantic.Field(alias="customFont")
]
+ upload: CustomFontUpload
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/custom_font_batch_create_response_failed_item.py b/src/webflow/types/custom_font_batch_create_response_failed_item.py
new file mode 100644
index 00000000..e00c56e7
--- /dev/null
+++ b/src/webflow/types/custom_font_batch_create_response_failed_item.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class CustomFontBatchCreateResponseFailedItem(UniversalBaseModel):
+ index: int = pydantic.Field()
+ """
+ Zero-based position of the font in the request `items` array
+ """
+
+ file_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="fileName"),
+ pydantic.Field(alias="fileName", description="The `fileName` of the font that could not be registered"),
+ ]
+ name: str = pydantic.Field()
+ """
+ Error name. `FontLimitReached` indicates the site's custom font limit was reached before this font could be registered.
+ """
+
+ msg: str = pydantic.Field()
+ """
+ Human-readable error message
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_batch_delete_response.py b/src/webflow/types/custom_font_batch_delete_response.py
new file mode 100644
index 00000000..42e78724
--- /dev/null
+++ b/src/webflow/types/custom_font_batch_delete_response.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .custom_font_batch_delete_response_deleted_item import CustomFontBatchDeleteResponseDeletedItem
+from .custom_font_batch_delete_response_failed_item import CustomFontBatchDeleteResponseFailedItem
+
+
+class CustomFontBatchDeleteResponse(UniversalBaseModel):
+ """
+ Per-item result of a bulk-delete operation
+ """
+
+ deleted: typing.List[CustomFontBatchDeleteResponseDeletedItem]
+ failed: typing.List[CustomFontBatchDeleteResponseFailedItem]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_batch_delete_response_deleted_item.py b/src/webflow/types/custom_font_batch_delete_response_deleted_item.py
new file mode 100644
index 00000000..6113e813
--- /dev/null
+++ b/src/webflow/types/custom_font_batch_delete_response_deleted_item.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CustomFontBatchDeleteResponseDeletedItem(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ The ID of a successfully deleted custom font
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_batch_delete_response_failed_item.py b/src/webflow/types/custom_font_batch_delete_response_failed_item.py
new file mode 100644
index 00000000..b61ca096
--- /dev/null
+++ b/src/webflow/types/custom_font_batch_delete_response_failed_item.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class CustomFontBatchDeleteResponseFailedItem(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ The ID of a custom font that could not be deleted
+ """
+
+ name: str = pydantic.Field()
+ """
+ Error name
+ """
+
+ msg: str = pydantic.Field()
+ """
+ Human-readable error message
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_create_response.py b/src/webflow/types/custom_font_create_response.py
new file mode 100644
index 00000000..a5aa156c
--- /dev/null
+++ b/src/webflow/types/custom_font_create_response.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .custom_font import CustomFont
+from .custom_font_upload import CustomFontUpload
+
+
+class CustomFontCreateResponse(UniversalBaseModel):
+ """
+ The response to a successful custom font creation or file replacement request
+ """
+
+ custom_font: typing_extensions.Annotated[
+ CustomFont, FieldMetadata(alias="customFont"), pydantic.Field(alias="customFont")
+ ]
+ upload: CustomFontUpload
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_font_display.py b/src/webflow/types/custom_font_font_display.py
new file mode 100644
index 00000000..bf19bca4
--- /dev/null
+++ b/src/webflow/types/custom_font_font_display.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CustomFontFontDisplay = typing.Union[typing.Literal["auto", "block", "swap", "fallback", "optional"], typing.Any]
diff --git a/src/webflow/types/custom_font_format.py b/src/webflow/types/custom_font_format.py
new file mode 100644
index 00000000..3f0635b9
--- /dev/null
+++ b/src/webflow/types/custom_font_format.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CustomFontFormat = typing.Union[
+ typing.Literal["woff2", "woff", "truetype", "opentype", "embedded-opentype", "svg"], typing.Any
+]
diff --git a/src/webflow/types/custom_font_upload.py b/src/webflow/types/custom_font_upload.py
new file mode 100644
index 00000000..b2c4b468
--- /dev/null
+++ b/src/webflow/types/custom_font_upload.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .custom_font_upload_fields import CustomFontUploadFields
+
+
+class CustomFontUpload(UniversalBaseModel):
+ """
+ Presigned S3 upload details. Post the font binary to `url` as `multipart/form-data`, including every key from `fields` plus the binary itself in a field named `file`. The `file` field must be the last field in the form.
+ """
+
+ url: str = pydantic.Field()
+ """
+ The S3 endpoint to POST the font binary to
+ """
+
+ fields: CustomFontUploadFields
+ expires_at: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="expiresAt"),
+ pydantic.Field(
+ alias="expiresAt",
+ description="ISO 8601 timestamp after which the presigned URL expires (approximately 15 minutes from issuance)",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_font_upload_fields.py b/src/webflow/types/custom_font_upload_fields.py
new file mode 100644
index 00000000..35552aa2
--- /dev/null
+++ b/src/webflow/types/custom_font_upload_fields.py
@@ -0,0 +1,94 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class CustomFontUploadFields(UniversalBaseModel):
+ """
+ Form fields to include in the S3 multipart POST. Every key must be sent as a form field before the `file` field.
+ """
+
+ bucket: str = pydantic.Field()
+ """
+ The S3 bucket name
+ """
+
+ key: str = pydantic.Field()
+ """
+ The S3 object key
+ """
+
+ policy: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="Policy"),
+ pydantic.Field(alias="Policy", description="Base64-encoded S3 policy document"),
+ ]
+ x_amz_algorithm: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="X-Amz-Algorithm"),
+ pydantic.Field(alias="X-Amz-Algorithm", description="AWS Signature Version 4 algorithm identifier"),
+ ]
+ x_amz_credential: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="X-Amz-Credential"),
+ pydantic.Field(alias="X-Amz-Credential", description="AWS credential string"),
+ ]
+ x_amz_date: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="X-Amz-Date"),
+ pydantic.Field(alias="X-Amz-Date", description="Request date in ISO 8601 basic format"),
+ ]
+ x_amz_signature: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="X-Amz-Signature"),
+ pydantic.Field(alias="X-Amz-Signature", description="AWS Signature Version 4 signature"),
+ ]
+ x_amz_security_token: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="X-Amz-Security-Token"),
+ pydantic.Field(
+ alias="X-Amz-Security-Token",
+ description="AWS security token (included when temporary credentials are used)",
+ ),
+ ] = None
+ content_md_5: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="Content-MD5"),
+ pydantic.Field(
+ alias="Content-MD5",
+ description="Base64-encoded MD5 hash of the file binary, derived from the `fileHash` you supplied",
+ ),
+ ]
+ acl: str = pydantic.Field()
+ """
+ S3 ACL. Always `public-read`, which makes the uploaded font accessible to the Webflow CDN.
+ """
+
+ cache_control: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="Cache-Control"),
+ pydantic.Field(alias="Cache-Control", description="Cache-Control header value applied to the S3 object"),
+ ]
+ content_type: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="Content-Type"),
+ pydantic.Field(alias="Content-Type", description="MIME type matched to the file extension"),
+ ]
+ success_action_status: str = pydantic.Field()
+ """
+ S3 returns this HTTP status code on a successful upload
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/custom_fonts.py b/src/webflow/types/custom_fonts.py
new file mode 100644
index 00000000..062b6718
--- /dev/null
+++ b/src/webflow/types/custom_fonts.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .custom_font import CustomFont
+from .pagination import Pagination
+
+
+class CustomFonts(UniversalBaseModel):
+ """
+ A list of custom fonts
+ """
+
+ custom_fonts: typing_extensions.Annotated[
+ typing.List[CustomFont], FieldMetadata(alias="customFonts"), pydantic.Field(alias="customFonts")
+ ]
+ pagination: Pagination
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/error_code.py b/src/webflow/types/error_code.py
index f9149eb2..56b0f0a0 100644
--- a/src/webflow/types/error_code.py
+++ b/src/webflow/types/error_code.py
@@ -4,35 +4,104 @@
ErrorCode = typing.Union[
typing.Literal[
+ "aeo_input_validation",
+ "analyze_filter_conflict",
+ "analyze_input_validation",
+ "analyze_unsupported_filter",
"bad_request",
+ "batch_too_large",
+ "before_historical_floor",
+ "billing_provider_unavailable",
+ "branch_merge_conflict",
+ "branch_merge_in_progress",
+ "branch_not_found",
+ "branch_publish_failed",
+ "branch_sync_pending",
+ "branch_task_not_found",
+ "branch_update_in_progress",
+ "brand_not_configured",
+ "cannot_invite_self",
+ "cannot_remove_self",
+ "cannot_update_user_role",
+ "client_seats_unavailable",
"collection_not_found",
+ "competitors_not_configured",
"conflict",
+ "conflicting_scope_filters",
+ "default_site_role_required",
"duplicate_collection",
+ "duplicate_member_in_request",
+ "duplicate_site_in_request",
"duplicate_user_email",
"ecommerce_not_enabled",
+ "end_time_too_recent",
+ "enterprise_provisioning_cloud_app_not_supported",
+ "enterprise_provisioning_upstream_error",
+ "enterprise_self_provisioning_not_entitled",
"forbidden",
"forms_require_republish",
"incompatible_webhook_filter",
+ "insufficient_permissions",
+ "insufficient_seats",
"internal_error",
"invalid_auth_version",
"invalid_credentials",
"invalid_domain",
+ "invalid_resolution",
+ "invalid_site_role",
+ "invalid_time_range",
"invalid_user_email",
+ "invalid_workspace_role",
+ "invite_expired",
+ "invite_role_conflict",
"item_not_found",
+ "managed_by_scim",
+ "member_already_has_access",
+ "member_has_no_site_access",
+ "member_is_paying_client",
+ "member_not_found",
+ "member_not_removable",
+ "member_role_conflict",
"missing_scopes",
"no_domains",
+ "no_members_or_invites",
"not_authorized",
"not_enterprise_plan_site",
"not_enterprise_plan_workspace",
+ "optimize_before_historical_floor",
+ "optimize_input_validation",
+ "optimize_invalid_interval",
+ "optimize_unknown_dimension",
+ "optimize_unknown_goal",
+ "optimize_unknown_optimization",
+ "optimize_unsupported_group_by",
"order_not_found",
+ "page_cannot_branch",
"resource_not_found",
+ "service_unavailable",
+ "site_access_not_applicable_to_member",
+ "site_access_not_scopable_for_role",
+ "site_access_roles_must_match",
+ "site_not_in_workspace",
+ "site_role_id_invalid",
+ "site_role_id_not_allowed",
+ "site_role_id_required",
+ "site_specific_access_disabled",
+ "site_specific_access_not_entitled",
+ "time_range_too_wide",
"too_many_requests",
+ "unsupported_bot_provider",
"unsupported_version",
"unsupported_webhook_trigger_type",
"user_limit_reached",
"user_not_found",
"users_not_enabled",
"validation_error",
+ "workspace_invite_not_found",
+ "workspace_member_managed_by_scim",
+ "workspace_member_not_found",
+ "workspace_member_not_removable",
+ "workspace_role_not_grantable",
],
typing.Any,
]
diff --git a/src/webflow/types/field_group.py b/src/webflow/types/field_group.py
new file mode 100644
index 00000000..af1ef62c
--- /dev/null
+++ b/src/webflow/types/field_group.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class FieldGroup(UniversalBaseModel):
+ """
+ A group of fields within a collection
+ """
+
+ display_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="displayName"),
+ pydantic.Field(alias="displayName", description="The display name of the field group"),
+ ]
+ field_ids: typing_extensions.Annotated[
+ typing.List[str],
+ FieldMetadata(alias="fieldIds"),
+ pydantic.Field(alias="fieldIds", description="The IDs of the fields in this group"),
+ ]
+ description: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ A description of the field group
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/field_validations_additional_properties.py b/src/webflow/types/field_validations_additional_properties.py
index 845d42fb..4e4a1dfb 100644
--- a/src/webflow/types/field_validations_additional_properties.py
+++ b/src/webflow/types/field_validations_additional_properties.py
@@ -2,10 +2,4 @@
import typing
-from .field_validations_additional_properties_additional_properties import (
- FieldValidationsAdditionalPropertiesAdditionalProperties,
-)
-
-FieldValidationsAdditionalProperties = typing.Union[
- str, float, bool, int, FieldValidationsAdditionalPropertiesAdditionalProperties
-]
+FieldValidationsAdditionalProperties = typing.Union[str, float, bool, int, typing.Any]
diff --git a/src/webflow/types/form_submission.py b/src/webflow/types/form_submission.py
index adf12b77..19301fd0 100644
--- a/src/webflow/types/form_submission.py
+++ b/src/webflow/types/form_submission.py
@@ -40,6 +40,14 @@ class FormSubmission(UniversalBaseModel):
FieldMetadata(alias="formResponse"),
pydantic.Field(alias="formResponse", description="The data submitted in the Form"),
] = None
+ locale_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="localeId"),
+ pydantic.Field(
+ alias="localeId",
+ description="The ID of the locale the form was submitted from. `null` for primary-locale submissions or sites without localization.",
+ ),
+ ] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/form_submission_trigger_payload.py b/src/webflow/types/form_submission_trigger_payload.py
index d7150490..43cf81fd 100644
--- a/src/webflow/types/form_submission_trigger_payload.py
+++ b/src/webflow/types/form_submission_trigger_payload.py
@@ -54,6 +54,14 @@ class FormSubmissionTriggerPayload(UniversalBaseModel):
FieldMetadata(alias="formElementId"),
pydantic.Field(alias="formElementId", description="The uniqueID of the Form element"),
] = None
+ locale_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="localeId"),
+ pydantic.Field(
+ alias="localeId",
+ description="The ID of the locale the form was submitted from. `null` for primary-locale submissions or sites without localization.",
+ ),
+ ] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/goal.py b/src/webflow/types/goal.py
new file mode 100644
index 00000000..276e1ab1
--- /dev/null
+++ b/src/webflow/types/goal.py
@@ -0,0 +1,90 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .goal_category import GoalCategory
+from .goal_integration_source import GoalIntegrationSource
+from .goal_scope import GoalScope
+from .goal_type import GoalType
+from .goal_value_type import GoalValueType
+
+
+class Goal(UniversalBaseModel):
+ """
+ A configured Analyze goal.
+
+ - `integrationSource` is present only when `goalType` is `integration`.
+ - `formId` is present only when `integrationSource` is `hubspot` or `marketo`.
+ - `valueType` is present only when `type` is `value`, and `value` only when `valueType` is `static`.
+ """
+
+ goal_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="goalId"),
+ pydantic.Field(
+ alias="goalId", description="Identifier of the goal. A numeric string, not a Webflow object ID."
+ ),
+ ]
+ name: str = pydantic.Field()
+ """
+ Display name of the goal.
+ """
+
+ is_site_goal: typing_extensions.Annotated[
+ bool,
+ FieldMetadata(alias="isSiteGoal"),
+ pydantic.Field(
+ alias="isSiteGoal",
+ description="Whether the goal is a site goal, one of the goals marked as your site's top-level conversions.",
+ ),
+ ]
+ goal_type: typing_extensions.Annotated[
+ GoalCategory, FieldMetadata(alias="goalType"), pydantic.Field(alias="goalType")
+ ]
+ integration_source: typing_extensions.Annotated[
+ typing.Optional[GoalIntegrationSource],
+ FieldMetadata(alias="integrationSource"),
+ pydantic.Field(alias="integrationSource"),
+ ] = None
+ form_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="formId"),
+ pydantic.Field(alias="formId", description="Identifier of the HubSpot or Marketo form the goal tracks."),
+ ] = None
+ scope: GoalScope
+ type: GoalType
+ event_ids: typing_extensions.Annotated[
+ typing.List[str],
+ FieldMetadata(alias="eventIds"),
+ pydantic.Field(
+ alias="eventIds",
+ description="Identifiers of the goal's underlying events. These are goal event IDs, not the element IDs returned by the top events report.",
+ ),
+ ]
+ value_type: typing_extensions.Annotated[
+ typing.Optional[GoalValueType], FieldMetadata(alias="valueType"), pydantic.Field(alias="valueType")
+ ] = None
+ value: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Static monetary amount in US cents (for example, `1800` is $18.00). Present only for `static` value goals.
+ """
+
+ updated_at: typing_extensions.Annotated[
+ typing.Optional[dt.datetime],
+ FieldMetadata(alias="updatedAt"),
+ pydantic.Field(alias="updatedAt", description="When the goal was last updated. Absent on older goals."),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/goal_category.py b/src/webflow/types/goal_category.py
new file mode 100644
index 00000000..631b8127
--- /dev/null
+++ b/src/webflow/types/goal_category.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalCategory = typing.Union[typing.Literal["click", "view", "integration", "custom"], typing.Any]
diff --git a/src/webflow/types/goal_integration_source.py b/src/webflow/types/goal_integration_source.py
new file mode 100644
index 00000000..836372d1
--- /dev/null
+++ b/src/webflow/types/goal_integration_source.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalIntegrationSource = typing.Union[typing.Literal["hubspot", "marketo", "shopify"], typing.Any]
diff --git a/src/webflow/types/goal_performance_data_point.py b/src/webflow/types/goal_performance_data_point.py
new file mode 100644
index 00000000..4f9fe076
--- /dev/null
+++ b/src/webflow/types/goal_performance_data_point.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class GoalPerformanceDataPoint(UniversalBaseModel):
+ """
+ Conversions for one day.
+ """
+
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ Start of the day bucket in `bucketTimeZone`, returned as a UTC instant.
+ """
+
+ count: float = pydantic.Field()
+ """
+ Unique conversions of the goal in this day.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/goal_performance_filter.py b/src/webflow/types/goal_performance_filter.py
new file mode 100644
index 00000000..15d35cea
--- /dev/null
+++ b/src/webflow/types/goal_performance_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class GoalPerformanceFilter(UniversalBaseModel):
+ """
+ Filter goal performance by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/goal_performance_response.py b/src/webflow/types/goal_performance_response.py
new file mode 100644
index 00000000..b57a1c5d
--- /dev/null
+++ b/src/webflow/types/goal_performance_response.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .analyze_daily_bucketing import AnalyzeDailyBucketing
+from .analyze_window import AnalyzeWindow
+from .goal import Goal
+from .goal_performance_data_point import GoalPerformanceDataPoint
+from .goal_performance_filter import GoalPerformanceFilter
+
+
+class GoalPerformanceResponse(UniversalBaseModel):
+ """
+ Response payload for goal performance. Returns the goal's configuration alongside its daily series, so callers don't need a separate List Goals request.
+ """
+
+ goal: Goal
+ window: AnalyzeWindow
+ bucketing: AnalyzeDailyBucketing
+ data: typing.List[GoalPerformanceDataPoint] = pydantic.Field()
+ """
+ Daily conversion counts, in chronological order. Empty when the goal had no conversions in the window.
+ """
+
+ filter: typing.Optional[GoalPerformanceFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/goal_scope.py b/src/webflow/types/goal_scope.py
new file mode 100644
index 00000000..eb280ca1
--- /dev/null
+++ b/src/webflow/types/goal_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalScope = typing.Union[typing.Literal["session", "user", "pageview"], typing.Any]
diff --git a/src/webflow/types/goal_type.py b/src/webflow/types/goal_type.py
new file mode 100644
index 00000000..810b33e0
--- /dev/null
+++ b/src/webflow/types/goal_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalType = typing.Union[typing.Literal["conversion", "value"], typing.Any]
diff --git a/src/webflow/types/goal_value_type.py b/src/webflow/types/goal_value_type.py
new file mode 100644
index 00000000..efa789f6
--- /dev/null
+++ b/src/webflow/types/goal_value_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+GoalValueType = typing.Union[typing.Literal["static", "dynamic"], typing.Any]
diff --git a/src/webflow/types/goals_response.py b/src/webflow/types/goals_response.py
new file mode 100644
index 00000000..989910f0
--- /dev/null
+++ b/src/webflow/types/goals_response.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .goal import Goal
+
+
+class GoalsResponse(UniversalBaseModel):
+ """
+ Response payload for the goals catalog.
+ """
+
+ goals: typing.List[Goal] = pydantic.Field()
+ """
+ The site's configured goals. Empty when the site has none.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/google_tag_id.py b/src/webflow/types/google_tag_id.py
new file mode 100644
index 00000000..6671d241
--- /dev/null
+++ b/src/webflow/types/google_tag_id.py
@@ -0,0 +1,42 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class GoogleTagId(UniversalBaseModel):
+ """
+ A Google Tag associated with a site.
+ """
+
+ order: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Display order of the tag. Optional on input; always present on output. Auto-assigned for new tags and renormalized after deletion.
+ """
+
+ display_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="displayName"),
+ pydantic.Field(alias="displayName", description="A human-readable label for the tag."),
+ ]
+ tag_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="tagId"),
+ pydantic.Field(
+ alias="tagId",
+ description="The Google Tag ID. Accepts G-, GT-, AW-, and DC- prefixes. Rejects UA- prefixes.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/google_tag_ids.py b/src/webflow/types/google_tag_ids.py
new file mode 100644
index 00000000..21cdb69a
--- /dev/null
+++ b/src/webflow/types/google_tag_ids.py
@@ -0,0 +1,30 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .google_tag_id import GoogleTagId
+
+
+class GoogleTagIds(UniversalBaseModel):
+ """
+ A list of Google Tags configured for a site.
+ """
+
+ google_tag_ids: typing_extensions.Annotated[
+ typing.List[GoogleTagId],
+ FieldMetadata(alias="googleTagIds"),
+ pydantic.Field(alias="googleTagIds", description="List of Google Tags configured for a site, sorted by order."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/insert_collection_item.py b/src/webflow/types/insert_collection_item.py
new file mode 100644
index 00000000..028f029f
--- /dev/null
+++ b/src/webflow/types/insert_collection_item.py
@@ -0,0 +1,66 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .insert_collection_item_field_data import InsertCollectionItemFieldData
+
+
+class InsertCollectionItem(UniversalBaseModel):
+ """
+ A single entry in an `items/insert` request. Each entry describes one item and the locales to create it in.
+
+ - Omit `id` to create a new item. Use `cmsLocaleIds` to select locales, or `allCmsLocales: true` to select every current site locale. The variants share one new item ID and the same `fieldData`. Omit both locale selectors to create only in the primary locale.
+ - Set `id` to an existing item's ID to add locale variants to that item. `cmsLocaleIds` is required when you send `id`, and the item must not already have a variant in those locales.
+ - To give one item different content per locale, send several entries that share the same `id`, each with a single locale in `cmsLocaleIds`.
+ """
+
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The ID of an existing item to attach locale variants to. Omit this property to create a new item. Requires `cmsLocaleIds` and cannot be combined with `allCmsLocales`.
+ """
+
+ cms_locale_ids: typing_extensions.Annotated[
+ typing.Optional[typing.List[str]],
+ FieldMetadata(alias="cmsLocaleIds"),
+ pydantic.Field(
+ alias="cmsLocaleIds",
+ description="The locales to create this entry in. For a new item, omit both this property and `allCmsLocales` to create only in the primary locale. Required when `id` is set. Cannot be combined with `allCmsLocales`, even as an empty array. A locale listed twice returns a `400`.",
+ ),
+ ] = None
+ all_cms_locales: typing_extensions.Annotated[
+ typing.Optional[typing.Literal[True]],
+ FieldMetadata(alias="allCmsLocales"),
+ pydantic.Field(
+ alias="allCmsLocales",
+ description="Set to `true` to create a new item in every current site locale, including the primary locale, without listing locale IDs. The variants share one new item ID and the same `fieldData`. Only `true` is accepted; omit this property instead of sending `false`. Cannot be combined with `cmsLocaleIds` or an existing item `id`. Each locale variant counts toward the 100-item request limit.",
+ ),
+ ] = None
+ is_archived: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isArchived"),
+ pydantic.Field(alias="isArchived", description="Boolean determining if the Item is set to archived"),
+ ] = None
+ is_draft: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isDraft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Whether the item is created in a draft state. A new item has never been published, so `isDraft: true` gives it a `Draft` status. Set `isDraft: false` to queue the item to publish on the next site publish.",
+ ),
+ ] = None
+ field_data: typing_extensions.Annotated[
+ InsertCollectionItemFieldData, FieldMetadata(alias="fieldData"), pydantic.Field(alias="fieldData")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/insert_collection_item_field_data.py b/src/webflow/types/insert_collection_item_field_data.py
new file mode 100644
index 00000000..a70ef69d
--- /dev/null
+++ b/src/webflow/types/insert_collection_item_field_data.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class InsertCollectionItemFieldData(UniversalBaseModel):
+ name: str = pydantic.Field()
+ """
+ Name of the Item
+ """
+
+ slug: str = pydantic.Field()
+ """
+ URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/inserted_collection_item.py b/src/webflow/types/inserted_collection_item.py
new file mode 100644
index 00000000..9345fcf2
--- /dev/null
+++ b/src/webflow/types/inserted_collection_item.py
@@ -0,0 +1,69 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .inserted_collection_item_field_data import InsertedCollectionItemFieldData
+
+
+class InsertedCollectionItem(UniversalBaseModel):
+ """
+ A Collection Item returned by `items/insert`. Locale variants of the same item share an `id` and differ by `cmsLocaleId`. An entry that selects several locales with `cmsLocaleIds` or `allCmsLocales` returns one item per locale, so match a returned item to the entry that created it on `id` and `cmsLocaleId` rather than on position.
+ """
+
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Unique identifier for the Item
+ """
+
+ cms_locale_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="cmsLocaleId"),
+ pydantic.Field(alias="cmsLocaleId", description="Identifier for the locale of the CMS item"),
+ ] = None
+ last_published: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="lastPublished"),
+ pydantic.Field(
+ alias="lastPublished",
+ description="The date the item was last published. `null` for the staged items this endpoint creates.",
+ ),
+ ] = None
+ last_updated: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="lastUpdated"),
+ pydantic.Field(alias="lastUpdated", description="The date the item was last updated"),
+ ] = None
+ created_on: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="createdOn"),
+ pydantic.Field(alias="createdOn", description="The date the item was created"),
+ ] = None
+ is_archived: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isArchived"),
+ pydantic.Field(alias="isArchived", description="Boolean determining if the Item is set to archived"),
+ ] = None
+ is_draft: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isDraft"),
+ pydantic.Field(
+ alias="isDraft",
+ description="Whether the item is in a draft state. `true` unless the request set `isDraft: false`.",
+ ),
+ ] = None
+ field_data: typing_extensions.Annotated[
+ InsertedCollectionItemFieldData, FieldMetadata(alias="fieldData"), pydantic.Field(alias="fieldData")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/inserted_collection_item_field_data.py b/src/webflow/types/inserted_collection_item_field_data.py
new file mode 100644
index 00000000..f7ae3e86
--- /dev/null
+++ b/src/webflow/types/inserted_collection_item_field_data.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class InsertedCollectionItemFieldData(UniversalBaseModel):
+ name: str = pydantic.Field()
+ """
+ Name of the Item
+ """
+
+ slug: str = pydantic.Field()
+ """
+ URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/invite_workspace_member_request_body.py b/src/webflow/types/invite_workspace_member_request_body.py
new file mode 100644
index 00000000..44fecef6
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .collaborator_client_invite import CollaboratorClientInvite
+from .with_default_site_role import WithDefaultSiteRole
+from .with_site_access import WithSiteAccess
+
+InviteWorkspaceMemberRequestBody = typing.Union[WithDefaultSiteRole, WithSiteAccess, CollaboratorClientInvite]
diff --git a/src/webflow/types/invite_workspace_member_request_body_default_site_role_default_site_role.py b/src/webflow/types/invite_workspace_member_request_body_default_site_role_default_site_role.py
new file mode 100644
index 00000000..4e1b4bb9
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_default_site_role_default_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/types/invite_workspace_member_request_body_default_site_role_workspace_role.py b/src/webflow/types/invite_workspace_member_request_body_default_site_role_workspace_role.py
new file mode 100644
index 00000000..ff6c4b45
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_default_site_role_workspace_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole = typing.Union[
+ typing.Literal["admin", "member", "reviewer"], typing.Any
+]
diff --git a/src/webflow/types/invite_workspace_member_request_body_email_site_access_item.py b/src/webflow/types/invite_workspace_member_request_body_email_site_access_item.py
new file mode 100644
index 00000000..4388ba32
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_email_site_access_item.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .invite_workspace_member_request_body_email_site_access_item_site_role import (
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole,
+)
+
+
+class InviteWorkspaceMemberRequestBodyEmailSiteAccessItem(UniversalBaseModel):
+ site_id: typing_extensions.Annotated[str, FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")]
+ site_role: typing_extensions.Annotated[
+ InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole,
+ FieldMetadata(alias="siteRole"),
+ pydantic.Field(alias="siteRole"),
+ ]
+ site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="siteRoleId"),
+ pydantic.Field(
+ alias="siteRoleId",
+ description="ID of the custom Site Role named by `siteRole`. Requires `siteRole` to be `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a client-eligible custom Site Role belonging to this workspace.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/invite_workspace_member_request_body_email_site_access_item_site_role.py b/src/webflow/types/invite_workspace_member_request_body_email_site_access_item_site_role.py
new file mode 100644
index 00000000..5f40beff
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_email_site_access_item_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+InviteWorkspaceMemberRequestBodyEmailSiteAccessItemSiteRole = typing.Union[
+ typing.Literal["marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/types/invite_workspace_member_request_body_one_site_access_item.py b/src/webflow/types/invite_workspace_member_request_body_one_site_access_item.py
new file mode 100644
index 00000000..82af639e
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_one_site_access_item.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .invite_workspace_member_request_body_one_site_access_item_site_role import (
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole,
+)
+
+
+class InviteWorkspaceMemberRequestBodyOneSiteAccessItem(UniversalBaseModel):
+ site_id: typing_extensions.Annotated[str, FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")]
+ site_role: typing_extensions.Annotated[
+ InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole,
+ FieldMetadata(alias="siteRole"),
+ pydantic.Field(alias="siteRole"),
+ ]
+ site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="siteRoleId"),
+ pydantic.Field(
+ alias="siteRoleId",
+ description="ID of the custom Site Role named by `siteRole`. Requires `siteRole` to be `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/invite_workspace_member_request_body_one_site_access_item_site_role.py b/src/webflow/types/invite_workspace_member_request_body_one_site_access_item_site_role.py
new file mode 100644
index 00000000..f175543e
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_one_site_access_item_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+InviteWorkspaceMemberRequestBodyOneSiteAccessItemSiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/types/invite_workspace_member_request_body_one_workspace_role.py b/src/webflow/types/invite_workspace_member_request_body_one_workspace_role.py
new file mode 100644
index 00000000..7ba41f97
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_request_body_one_workspace_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+InviteWorkspaceMemberRequestBodyOneWorkspaceRole = typing.Union[
+ typing.Literal["admin", "member", "reviewer"], typing.Any
+]
diff --git a/src/webflow/types/invite_workspace_member_response_body.py b/src/webflow/types/invite_workspace_member_response_body.py
new file mode 100644
index 00000000..1e0c4c90
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_response_body.py
@@ -0,0 +1,53 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_invite import WorkspaceInvite
+from .workspace_member import WorkspaceMember
+
+
+class InviteWorkspaceMemberResponseBody_Invited(UniversalBaseModel):
+ """
+ `invited` with the invite when the request creates or extends an invite, including when an active client is invited to an additional Site. `active` means the requested membership and Site access already exist.
+ """
+
+ status: typing.Literal["invited"] = "invited"
+ invite: WorkspaceInvite
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+class InviteWorkspaceMemberResponseBody_Active(UniversalBaseModel):
+ """
+ `invited` with the invite when the request creates or extends an invite, including when an active client is invited to an additional Site. `active` means the requested membership and Site access already exist.
+ """
+
+ status: typing.Literal["active"] = "active"
+ member: WorkspaceMember
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+InviteWorkspaceMemberResponseBody = typing_extensions.Annotated[
+ typing.Union[InviteWorkspaceMemberResponseBody_Invited, InviteWorkspaceMemberResponseBody_Active],
+ pydantic.Field(discriminator="status"),
+]
diff --git a/src/webflow/types/invite_workspace_member_response_body_active.py b/src/webflow/types/invite_workspace_member_response_body_active.py
new file mode 100644
index 00000000..6a72857d
--- /dev/null
+++ b/src/webflow/types/invite_workspace_member_response_body_active.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_member import WorkspaceMember
+
+
+class InviteWorkspaceMemberResponseBodyActive(UniversalBaseModel):
+ member: WorkspaceMember
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_audit_log_item_actor.py b/src/webflow/types/invited.py
similarity index 78%
rename from src/webflow/types/workspace_audit_log_item_actor.py
rename to src/webflow/types/invited.py
index ced71491..ddd4cff8 100644
--- a/src/webflow/types/workspace_audit_log_item_actor.py
+++ b/src/webflow/types/invited.py
@@ -4,11 +4,11 @@
import pydantic
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_invite import WorkspaceInvite
-class WorkspaceAuditLogItemActor(UniversalBaseModel):
- id: typing.Optional[str] = None
- email: typing.Optional[str] = None
+class Invited(UniversalBaseModel):
+ invite: WorkspaceInvite
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/items_list_items_live_request_created_on.py b/src/webflow/types/items_list_items_live_request_created_on.py
new file mode 100644
index 00000000..93a7ec1b
--- /dev/null
+++ b/src/webflow/types/items_list_items_live_request_created_on.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ItemsListItemsLiveRequestCreatedOn(UniversalBaseModel):
+ lte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items created before this date
+ """
+
+ gte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items created after this date
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/items_list_items_live_request_last_updated.py b/src/webflow/types/items_list_items_live_request_last_updated.py
new file mode 100644
index 00000000..fc621f85
--- /dev/null
+++ b/src/webflow/types/items_list_items_live_request_last_updated.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ItemsListItemsLiveRequestLastUpdated(UniversalBaseModel):
+ lte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items last updated before this date
+ """
+
+ gte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items last updated after this date
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/items_list_items_request_created_on.py b/src/webflow/types/items_list_items_request_created_on.py
new file mode 100644
index 00000000..f67078ce
--- /dev/null
+++ b/src/webflow/types/items_list_items_request_created_on.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ItemsListItemsRequestCreatedOn(UniversalBaseModel):
+ lte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items created before this date
+ """
+
+ gte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items created after this date
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/items_list_items_request_last_updated.py b/src/webflow/types/items_list_items_request_last_updated.py
new file mode 100644
index 00000000..cd3e1ef2
--- /dev/null
+++ b/src/webflow/types/items_list_items_request_last_updated.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class ItemsListItemsRequestLastUpdated(UniversalBaseModel):
+ lte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items last updated before this date
+ """
+
+ gte: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ Filter items last updated after this date
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization.py b/src/webflow/types/optimization.py
new file mode 100644
index 00000000..98245d13
--- /dev/null
+++ b/src/webflow/types/optimization.py
@@ -0,0 +1,66 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimization_interval import OptimizationInterval
+from .optimization_status import OptimizationStatus
+from .optimization_type import OptimizationType
+from .optimize_current_goal import OptimizeCurrentGoal
+
+
+class Optimization(UniversalBaseModel):
+ """
+ An optimization is a container for one or more variations. It defines where (the pages), what (the changes, grouped into variations), and who (which visitors see each variation).
+ """
+
+ optimization_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="optimizationId"),
+ pydantic.Field(alias="optimizationId", description="The optimization ID."),
+ ]
+ name: str = pydantic.Field()
+ """
+ The optimization's name.
+ """
+
+ optimization_type: typing_extensions.Annotated[
+ OptimizationType, FieldMetadata(alias="optimizationType"), pydantic.Field(alias="optimizationType")
+ ]
+ status: OptimizationStatus
+ variation_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="variationCount"),
+ pydantic.Field(alias="variationCount", description="The number of variations in the optimization."),
+ ]
+ updated_at: typing_extensions.Annotated[
+ typing.Optional[dt.datetime],
+ FieldMetadata(alias="updatedAt"),
+ pydantic.Field(alias="updatedAt", description="When the optimization was last updated."),
+ ] = None
+ intervals: typing.List[OptimizationInterval] = pydantic.Field()
+ """
+ The optimization's intervals, oldest first. Empty for an optimization that has never run.
+ """
+
+ current_goals: typing_extensions.Annotated[
+ typing.List[OptimizeCurrentGoal],
+ FieldMetadata(alias="currentGoals"),
+ pydantic.Field(
+ alias="currentGoals",
+ description="The optimization's goals as they're set up now: its site goals, the existing goals added to it, and the goals created in it. `source` shows where each goal comes from. The list is the same for every interval, and optimizations that have never run have it too. It isn't in a set order.\n\nA report accepts any `goalId` from this list, in any interval.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_audience_insights_response.py b/src/webflow/types/optimization_audience_insights_response.py
new file mode 100644
index 00000000..0644ce4b
--- /dev/null
+++ b/src/webflow/types/optimization_audience_insights_response.py
@@ -0,0 +1,58 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimization_type import OptimizationType
+from .optimize_applied_filter import OptimizeAppliedFilter
+from .optimize_audience_variation import OptimizeAudienceVariation
+from .optimize_dimension import OptimizeDimension
+from .optimize_report_goal import OptimizeReportGoal
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+
+
+class OptimizationAudienceInsightsResponse(UniversalBaseModel):
+ """
+ Each variation's results for each segment of one dimension, for one goal over a date range within one interval.
+ """
+
+ report: typing.Literal["optimization_audience_insights"] = pydantic.Field(default="optimization_audience_insights")
+ """
+ Identifies the report type.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ goal: OptimizeReportGoal
+ optimization_type: typing_extensions.Annotated[
+ OptimizationType, FieldMetadata(alias="optimizationType"), pydantic.Field(alias="optimizationType")
+ ]
+ grouped_by: typing_extensions.Annotated[
+ OptimizeDimension,
+ FieldMetadata(alias="groupedBy"),
+ pydantic.Field(
+ alias="groupedBy", description="The dimension the segments are grouped by, from the request's `groupBy`."
+ ),
+ ]
+ filter: typing.Optional[OptimizeAppliedFilter] = None
+ variations: typing.List[OptimizeAudienceVariation] = pydantic.Field()
+ """
+ One entry per variation.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_dimension_values_response.py b/src/webflow/types/optimization_dimension_values_response.py
new file mode 100644
index 00000000..8262dd60
--- /dev/null
+++ b/src/webflow/types/optimization_dimension_values_response.py
@@ -0,0 +1,54 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_applied_filter import OptimizeAppliedFilter
+from .optimize_dimension import OptimizeDimension
+from .optimize_dimension_value import OptimizeDimensionValue
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+
+
+class OptimizationDimensionValuesResponse(UniversalBaseModel):
+ """
+ A page of values seen for one dimension.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ dimension: OptimizeDimension = pydantic.Field()
+ """
+ The dimension the values belong to.
+ """
+
+ filter: typing.Optional[OptimizeAppliedFilter] = None
+ values: typing.List[OptimizeDimensionValue] = pydantic.Field()
+ """
+ The values on this page, sorted by `eventCount`, highest first. Empty if the dimension had no values in the date range.
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Pass this value as `cursor` to get the next page. Omitted on the last page.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_dimensions_response.py b/src/webflow/types/optimization_dimensions_response.py
new file mode 100644
index 00000000..e96db50b
--- /dev/null
+++ b/src/webflow/types/optimization_dimensions_response.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_dimension import OptimizeDimension
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+
+
+class OptimizationDimensionsResponse(UniversalBaseModel):
+ """
+ The dimensions you can group or filter an optimization's reports by.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ dimensions: typing.List[OptimizeDimension] = pydantic.Field()
+ """
+ The available dimensions.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_goals_response.py b/src/webflow/types/optimization_goals_response.py
new file mode 100644
index 00000000..7c1ffa04
--- /dev/null
+++ b/src/webflow/types/optimization_goals_response.py
@@ -0,0 +1,47 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_current_goal import OptimizeCurrentGoal
+from .optimize_goal import OptimizeGoal
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+
+
+class OptimizationGoalsResponse(UniversalBaseModel):
+ """
+ An optimization's goals for the interval that contains the requested date range, and its current goals.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ goals: typing.List[OptimizeGoal] = pydantic.Field()
+ """
+ The goals saved when the interval started. They include the interval's target goals and the goals created in the optimization by then.
+ """
+
+ current_goals: typing_extensions.Annotated[
+ typing.List[OptimizeCurrentGoal],
+ FieldMetadata(alias="currentGoals"),
+ pydantic.Field(
+ alias="currentGoals",
+ description="The optimization's goals as they're set up now, with where each comes from. The list is the same for every interval. A report accepts any `goalId` from this list.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_interval.py b/src/webflow/types/optimization_interval.py
new file mode 100644
index 00000000..eaf0c69b
--- /dev/null
+++ b/src/webflow/types/optimization_interval.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .optimize_goal import OptimizeGoal
+
+
+class OptimizationInterval(UniversalBaseModel):
+ """
+ An interval is a snapshot of the optimization's configuration, such as which variations are live and which goals are target goals. When the configuration changes in a way that affects results, the current interval ends and a new one begins.
+
+ Each report covers a date range within one interval. Use an interval's `start` and `end` to choose `intervalStart` and `intervalEnd`.
+ """
+
+ start: dt.datetime = pydantic.Field()
+ """
+ When the interval began.
+ """
+
+ end: typing.Optional[dt.datetime] = pydantic.Field(default=None)
+ """
+ When the interval ended. Omitted while the interval is ongoing.
+ """
+
+ goals: typing.List[OptimizeGoal] = pydantic.Field()
+ """
+ The goals saved when this interval started. They include the interval's target goals and the goals created in the optimization by then. Goals created later, and site goals and existing goals that aren't a target goal, appear only in the optimization's `currentGoals`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_list_response.py b/src/webflow/types/optimization_list_response.py
new file mode 100644
index 00000000..d29a9532
--- /dev/null
+++ b/src/webflow/types/optimization_list_response.py
@@ -0,0 +1,44 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimization import Optimization
+from .optimization_status import OptimizationStatus
+
+
+class OptimizationListResponse(UniversalBaseModel):
+ """
+ A page of optimizations.
+ """
+
+ optimizations: typing.List[Optimization] = pydantic.Field()
+ """
+ The optimizations on this page, sorted by `optimizationId`.
+ """
+
+ status: typing.List[OptimizationStatus] = pydantic.Field()
+ """
+ The statuses this list includes.
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Pass this value as `cursor` to get the next page. Omitted on the last page.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_status.py b/src/webflow/types/optimization_status.py
new file mode 100644
index 00000000..b9bdc786
--- /dev/null
+++ b/src/webflow/types/optimization_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizationStatus = typing.Union[typing.Literal["live", "draft", "ready", "archived", "off"], typing.Any]
diff --git a/src/webflow/types/optimization_summary_response.py b/src/webflow/types/optimization_summary_response.py
new file mode 100644
index 00000000..d0730dc6
--- /dev/null
+++ b/src/webflow/types/optimization_summary_response.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimization_type import OptimizationType
+from .optimize_applied_filter import OptimizeAppliedFilter
+from .optimize_report_goal import OptimizeReportGoal
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+from .optimize_variation_result import OptimizeVariationResult
+
+
+class OptimizationSummaryResponse(UniversalBaseModel):
+ """
+ Each variation's results for one goal over a date range within one interval.
+ """
+
+ report: typing.Literal["optimization_summary"] = pydantic.Field(default="optimization_summary")
+ """
+ Identifies the report type.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ goal: OptimizeReportGoal
+ optimization_type: typing_extensions.Annotated[
+ OptimizationType, FieldMetadata(alias="optimizationType"), pydantic.Field(alias="optimizationType")
+ ]
+ filter: typing.Optional[OptimizeAppliedFilter] = None
+ variations: typing.List[OptimizeVariationResult] = pydantic.Field()
+ """
+ One entry per variation.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_trend_response.py b/src/webflow/types/optimization_trend_response.py
new file mode 100644
index 00000000..cdf3e5ba
--- /dev/null
+++ b/src/webflow/types/optimization_trend_response.py
@@ -0,0 +1,60 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimization_type import OptimizationType
+from .optimize_applied_filter import OptimizeAppliedFilter
+from .optimize_report_goal import OptimizeReportGoal
+from .optimize_report_interval_end import OptimizeReportIntervalEnd
+from .optimize_report_interval_start import OptimizeReportIntervalStart
+from .optimize_trend_granularity import OptimizeTrendGranularity
+from .optimize_trend_point import OptimizeTrendPoint
+
+
+class OptimizationTrendResponse(UniversalBaseModel):
+ """
+ Each variation's results by day or week, for one goal over a date range within one interval.
+ """
+
+ report: typing.Literal["optimization_trend"] = pydantic.Field(default="optimization_trend")
+ """
+ Identifies the report type.
+ """
+
+ interval_start: typing_extensions.Annotated[
+ OptimizeReportIntervalStart, FieldMetadata(alias="intervalStart"), pydantic.Field(alias="intervalStart")
+ ]
+ interval_end: typing_extensions.Annotated[
+ OptimizeReportIntervalEnd, FieldMetadata(alias="intervalEnd"), pydantic.Field(alias="intervalEnd")
+ ]
+ goal: OptimizeReportGoal
+ granularity: OptimizeTrendGranularity
+ time_zone: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="timeZone"),
+ pydantic.Field(
+ alias="timeZone",
+ description="The time zone the buckets use. This is the account's time zone, or `America/Los_Angeles` if none is set.",
+ ),
+ ]
+ optimization_type: typing_extensions.Annotated[
+ OptimizationType, FieldMetadata(alias="optimizationType"), pydantic.Field(alias="optimizationType")
+ ]
+ filter: typing.Optional[OptimizeAppliedFilter] = None
+ series: typing.List[OptimizeTrendPoint] = pydantic.Field()
+ """
+ One entry per bucket.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimization_type.py b/src/webflow/types/optimization_type.py
new file mode 100644
index 00000000..89388394
--- /dev/null
+++ b/src/webflow/types/optimization_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizationType = typing.Union[typing.Literal["test", "personalize", "aiOptimize"], typing.Any]
diff --git a/src/webflow/types/optimize_applied_filter.py b/src/webflow/types/optimize_applied_filter.py
new file mode 100644
index 00000000..9faf9f7d
--- /dev/null
+++ b/src/webflow/types/optimize_applied_filter.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .optimize_applied_filter_entry import OptimizeAppliedFilterEntry
+
+OptimizeAppliedFilter = typing.Dict[str, OptimizeAppliedFilterEntry]
diff --git a/src/webflow/types/optimize_applied_filter_entry.py b/src/webflow/types/optimize_applied_filter_entry.py
new file mode 100644
index 00000000..b7a7941a
--- /dev/null
+++ b/src/webflow/types/optimize_applied_filter_entry.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeAppliedFilterEntry(UniversalBaseModel):
+ """
+ The filter applied for one dimension.
+ """
+
+ name: str = pydantic.Field()
+ """
+ The dimension's display name.
+ """
+
+ in_: typing_extensions.Annotated[
+ typing.Optional[typing.List[str]],
+ FieldMetadata(alias="in"),
+ pydantic.Field(alias="in", description="The included values."),
+ ] = None
+ nin: typing.Optional[typing.List[str]] = pydantic.Field(default=None)
+ """
+ The excluded values.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_audience_segment.py b/src/webflow/types/optimize_audience_segment.py
new file mode 100644
index 00000000..8c9371d6
--- /dev/null
+++ b/src/webflow/types/optimize_audience_segment.py
@@ -0,0 +1,80 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_trend_direction import OptimizeTrendDirection
+
+
+class OptimizeAudienceSegment(UniversalBaseModel):
+ """
+ One variation's results for one segment of the `groupBy` dimension.
+ """
+
+ segment: str = pydantic.Field()
+ """
+ The segment's value for the `groupBy` dimension.
+ """
+
+ segment_label: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="segmentLabel"),
+ pydantic.Field(alias="segmentLabel", description="The segment's display label."),
+ ]
+ sessions: float = pydantic.Field()
+ """
+ The number of sessions in this segment that saw the variation.
+ """
+
+ users: float = pydantic.Field()
+ """
+ The number of users in this segment who saw the variation.
+ """
+
+ conversions: float = pydantic.Field()
+ """
+ The number of conversions in this segment.
+ """
+
+ conversion_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="conversionRate"),
+ pydantic.Field(
+ alias="conversionRate",
+ description="The conversion rate in this segment, as a fraction. For example, `0.19` means 19%.",
+ ),
+ ]
+ value: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ The total value of this segment's conversions. For `value` goals only.
+ """
+
+ lift: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ The variation's lift in this segment, as a fraction. For example, `0.19` means 19%. For `conversion` goals only.
+ """
+
+ trend_direction: typing_extensions.Annotated[
+ typing.Optional[OptimizeTrendDirection],
+ FieldMetadata(alias="trendDirection"),
+ pydantic.Field(alias="trendDirection"),
+ ] = None
+ confidence: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ A value from 0 to 1 for how much to trust `trendDirection`.
+ - For `up` and `down`, the confidence that the difference from the base variation is real.
+ - For `sideways`, the confidence that a 10% difference would have been detected.
+ - Always `0` for `unsure`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_audience_variation.py b/src/webflow/types/optimize_audience_variation.py
new file mode 100644
index 00000000..b350c17c
--- /dev/null
+++ b/src/webflow/types/optimize_audience_variation.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_audience_segment import OptimizeAudienceSegment
+
+
+class OptimizeAudienceVariation(UniversalBaseModel):
+ """
+ One variation's results, split by segment.
+ """
+
+ variation_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="variationId"), pydantic.Field(alias="variationId", description="The variation ID.")
+ ]
+ name: str = pydantic.Field()
+ """
+ The variation's name.
+ """
+
+ segments: typing.List[OptimizeAudienceSegment] = pydantic.Field()
+ """
+ One entry per segment of the `groupBy` dimension.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_current_goal.py b/src/webflow/types/optimize_current_goal.py
new file mode 100644
index 00000000..6530e7b7
--- /dev/null
+++ b/src/webflow/types/optimize_current_goal.py
@@ -0,0 +1,49 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_goal_counting_method import OptimizeGoalCountingMethod
+from .optimize_goal_metric_type import OptimizeGoalMetricType
+from .optimize_goal_scope import OptimizeGoalScope
+from .optimize_goal_source import OptimizeGoalSource
+
+
+class OptimizeCurrentGoal(UniversalBaseModel):
+ """
+ A goal as it's set up on the optimization now. It has no `isPrimary`, because target goals are set per interval.
+ """
+
+ goal_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="goalId"),
+ pydantic.Field(
+ alias="goalId",
+ description="The goal ID. Pass it as `goalId` to choose the goal a report measures, in any interval.",
+ ),
+ ]
+ name: str = pydantic.Field()
+ """
+ The goal's name.
+ """
+
+ metric_type: typing_extensions.Annotated[
+ OptimizeGoalMetricType, FieldMetadata(alias="metricType"), pydantic.Field(alias="metricType")
+ ]
+ scope: OptimizeGoalScope
+ counting_method: typing_extensions.Annotated[
+ OptimizeGoalCountingMethod, FieldMetadata(alias="countingMethod"), pydantic.Field(alias="countingMethod")
+ ]
+ source: OptimizeGoalSource
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_dimension.py b/src/webflow/types/optimize_dimension.py
new file mode 100644
index 00000000..2fbacc68
--- /dev/null
+++ b/src/webflow/types/optimize_dimension.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class OptimizeDimension(UniversalBaseModel):
+ """
+ A dimension you can group or filter an optimization's reports by.
+ """
+
+ id: str = pydantic.Field()
+ """
+ The dimension ID. Use it in `groupBy`, as a `filter` key, or to list the dimension's values.
+ """
+
+ name: str = pydantic.Field()
+ """
+ The dimension's display name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_dimension_value.py b/src/webflow/types/optimize_dimension_value.py
new file mode 100644
index 00000000..67d7346b
--- /dev/null
+++ b/src/webflow/types/optimize_dimension_value.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeDimensionValue(UniversalBaseModel):
+ """
+ A value seen for the dimension.
+ """
+
+ value: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The raw value. Use it in `filter` to narrow a report. `null` means the dimension was absent.
+ """
+
+ label: str = pydantic.Field()
+ """
+ The value's display label.
+ """
+
+ event_count: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="eventCount"),
+ pydantic.Field(alias="eventCount", description="How often the value appeared in the date range."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_filter.py b/src/webflow/types/optimize_filter.py
new file mode 100644
index 00000000..7e966312
--- /dev/null
+++ b/src/webflow/types/optimize_filter.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .optimize_filter_operators import OptimizeFilterOperators
+
+OptimizeFilter = typing.Dict[str, OptimizeFilterOperators]
diff --git a/src/webflow/types/optimize_filter_operators.py b/src/webflow/types/optimize_filter_operators.py
new file mode 100644
index 00000000..4676eead
--- /dev/null
+++ b/src/webflow/types/optimize_filter_operators.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeFilterOperators(UniversalBaseModel):
+ """
+ Values to include or exclude for one dimension. Specify at least one of `in` or `nin`. Values within one dimension combine with OR.
+ """
+
+ in_: typing_extensions.Annotated[
+ typing.Optional[typing.List[str]],
+ FieldMetadata(alias="in"),
+ pydantic.Field(
+ alias="in",
+ description="Include visitors whose value for this dimension is in this list. Use indexed bracket notation, for example `filter[country][in][0]=US&filter[country][in][1]=GB`.",
+ ),
+ ] = None
+ nin: typing.Optional[typing.List[str]] = pydantic.Field(default=None)
+ """
+ Exclude visitors whose value for this dimension is in this list. Use indexed bracket notation, for example `filter[customAttribute:towel][nin][0]=forgotten`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_goal.py b/src/webflow/types/optimize_goal.py
new file mode 100644
index 00000000..c78c2548
--- /dev/null
+++ b/src/webflow/types/optimize_goal.py
@@ -0,0 +1,54 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_goal_counting_method import OptimizeGoalCountingMethod
+from .optimize_goal_metric_type import OptimizeGoalMetricType
+from .optimize_goal_scope import OptimizeGoalScope
+
+
+class OptimizeGoal(UniversalBaseModel):
+ """
+ A goal. Results are measured against goals. If a visitor takes the action defined in a goal, that counts as a conversion.
+ """
+
+ goal_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="goalId"),
+ pydantic.Field(
+ alias="goalId", description="The goal ID. Pass it as `goalId` to choose the goal a report measures."
+ ),
+ ]
+ name: str = pydantic.Field()
+ """
+ The goal's name.
+ """
+
+ is_primary: typing_extensions.Annotated[
+ bool,
+ FieldMetadata(alias="isPrimary"),
+ pydantic.Field(
+ alias="isPrimary",
+ description="`true` for a target goal, a goal Optimize uses to measure success. An interval usually has one target goal, but it can have several or none. Other goals are used only for reporting.",
+ ),
+ ]
+ metric_type: typing_extensions.Annotated[
+ OptimizeGoalMetricType, FieldMetadata(alias="metricType"), pydantic.Field(alias="metricType")
+ ]
+ scope: OptimizeGoalScope
+ counting_method: typing_extensions.Annotated[
+ OptimizeGoalCountingMethod, FieldMetadata(alias="countingMethod"), pydantic.Field(alias="countingMethod")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_goal_counting_method.py b/src/webflow/types/optimize_goal_counting_method.py
new file mode 100644
index 00000000..b0e9cf5c
--- /dev/null
+++ b/src/webflow/types/optimize_goal_counting_method.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeGoalCountingMethod = typing.Union[typing.Literal["unique", "cumulative"], typing.Any]
diff --git a/src/webflow/types/optimize_goal_metric_type.py b/src/webflow/types/optimize_goal_metric_type.py
new file mode 100644
index 00000000..92b584f8
--- /dev/null
+++ b/src/webflow/types/optimize_goal_metric_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeGoalMetricType = typing.Union[typing.Literal["conversion", "value"], typing.Any]
diff --git a/src/webflow/types/optimize_goal_scope.py b/src/webflow/types/optimize_goal_scope.py
new file mode 100644
index 00000000..39b4b8a9
--- /dev/null
+++ b/src/webflow/types/optimize_goal_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeGoalScope = typing.Union[typing.Literal["pageview", "user", "session"], typing.Any]
diff --git a/src/webflow/types/optimize_goal_source.py b/src/webflow/types/optimize_goal_source.py
new file mode 100644
index 00000000..a445da24
--- /dev/null
+++ b/src/webflow/types/optimize_goal_source.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeGoalSource = typing.Union[typing.Literal["site", "existing", "optimization"], typing.Any]
diff --git a/src/webflow/types/optimize_non_webflow_site_resource.py b/src/webflow/types/optimize_non_webflow_site_resource.py
new file mode 100644
index 00000000..6acd91b7
--- /dev/null
+++ b/src/webflow/types/optimize_non_webflow_site_resource.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeNonWebflowSiteResource(UniversalBaseModel):
+ """
+ A non-Webflow site that uses Optimize.
+ """
+
+ optimize_site_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="optimizeSiteId"),
+ pydantic.Field(alias="optimizeSiteId", description="The ID of the non-Webflow site."),
+ ]
+ workspace_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="workspaceId"),
+ pydantic.Field(alias="workspaceId", description="The ID of the workspace the site belongs to."),
+ ] = None
+ name: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The site's name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_reference.py b/src/webflow/types/optimize_reference.py
new file mode 100644
index 00000000..dec4ee58
--- /dev/null
+++ b/src/webflow/types/optimize_reference.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_reference_resource import OptimizeReferenceResource
+
+
+class OptimizeReference(UniversalBaseModel):
+ """
+ An `optimizeId` the access token can access, and the resource it comes from.
+ """
+
+ optimize_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="optimizeId"),
+ pydantic.Field(
+ alias="optimizeId",
+ description="The ID of an Optimize configuration, which holds its optimizations, goals, audiences, and settings. Use it with the other Optimize endpoints.",
+ ),
+ ]
+ resource: OptimizeReferenceResource = pydantic.Field()
+ """
+ The resource the `optimizeId` comes from. `type` identifies which kind.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_reference_list_response.py b/src/webflow/types/optimize_reference_list_response.py
new file mode 100644
index 00000000..7f14fe08
--- /dev/null
+++ b/src/webflow/types/optimize_reference_list_response.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .optimize_reference import OptimizeReference
+
+
+class OptimizeReferenceListResponse(UniversalBaseModel):
+ """
+ The `optimizeId` values the access token can access.
+ """
+
+ references: typing.List[OptimizeReference] = pydantic.Field()
+ """
+ One entry per `optimizeId` and resource, sorted by `optimizeId`. Not paginated.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_reference_resource.py b/src/webflow/types/optimize_reference_resource.py
new file mode 100644
index 00000000..7b1b4c87
--- /dev/null
+++ b/src/webflow/types/optimize_reference_resource.py
@@ -0,0 +1,64 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeReferenceResource_WebflowSite(UniversalBaseModel):
+ """
+ The resource the `optimizeId` comes from. `type` identifies which kind.
+ """
+
+ type: typing.Literal["webflow-site"] = "webflow-site"
+ site_id: typing_extensions.Annotated[str, FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId")]
+ workspace_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="workspaceId"), pydantic.Field(alias="workspaceId")
+ ] = None
+ display_name: typing_extensions.Annotated[
+ str, FieldMetadata(alias="displayName"), pydantic.Field(alias="displayName")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+class OptimizeReferenceResource_OptimizeSite(UniversalBaseModel):
+ """
+ The resource the `optimizeId` comes from. `type` identifies which kind.
+ """
+
+ type: typing.Literal["optimize-site"] = "optimize-site"
+ optimize_site_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="optimizeSiteId"), pydantic.Field(alias="optimizeSiteId")
+ ]
+ workspace_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="workspaceId"), pydantic.Field(alias="workspaceId")
+ ] = None
+ name: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+OptimizeReferenceResource = typing_extensions.Annotated[
+ typing.Union[OptimizeReferenceResource_WebflowSite, OptimizeReferenceResource_OptimizeSite],
+ pydantic.Field(discriminator="type"),
+]
diff --git a/src/webflow/types/optimize_report_goal.py b/src/webflow/types/optimize_report_goal.py
new file mode 100644
index 00000000..35210ce7
--- /dev/null
+++ b/src/webflow/types/optimize_report_goal.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeReportGoal(UniversalBaseModel):
+ """
+ The goal the report measures. When the request omits `goalId`, this is the interval's first target goal, or its first goal if it has no target goal.
+ """
+
+ goal_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="goalId"), pydantic.Field(alias="goalId", description="The goal ID.")
+ ]
+ name: str = pydantic.Field()
+ """
+ The goal's name.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_report_interval_end.py b/src/webflow/types/optimize_report_interval_end.py
new file mode 100644
index 00000000..85b2e66b
--- /dev/null
+++ b/src/webflow/types/optimize_report_interval_end.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+
+OptimizeReportIntervalEnd = dt.datetime
diff --git a/src/webflow/types/optimize_report_interval_start.py b/src/webflow/types/optimize_report_interval_start.py
new file mode 100644
index 00000000..fb6771dd
--- /dev/null
+++ b/src/webflow/types/optimize_report_interval_start.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+
+OptimizeReportIntervalStart = dt.datetime
diff --git a/src/webflow/types/optimize_trend_direction.py b/src/webflow/types/optimize_trend_direction.py
new file mode 100644
index 00000000..98e14386
--- /dev/null
+++ b/src/webflow/types/optimize_trend_direction.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeTrendDirection = typing.Union[typing.Literal["up", "down", "sideways", "unsure"], typing.Any]
diff --git a/src/webflow/types/optimize_trend_granularity.py b/src/webflow/types/optimize_trend_granularity.py
new file mode 100644
index 00000000..090000e9
--- /dev/null
+++ b/src/webflow/types/optimize_trend_granularity.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+OptimizeTrendGranularity = typing.Union[typing.Literal["daily", "weekly"], typing.Any]
diff --git a/src/webflow/types/optimize_trend_point.py b/src/webflow/types/optimize_trend_point.py
new file mode 100644
index 00000000..8b6209fb
--- /dev/null
+++ b/src/webflow/types/optimize_trend_point.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .optimize_variation_result import OptimizeVariationResult
+
+
+class OptimizeTrendPoint(UniversalBaseModel):
+ """
+ Each variation's results for one bucket.
+ """
+
+ date: dt.datetime = pydantic.Field()
+ """
+ The start of the day or week in `timeZone`, as a UTC timestamp. For example, with `timeZone` set to `America/Chicago`, the bucket for September 3 has a `date` of `2026-09-03T05:00:00.000Z`.
+ """
+
+ variations: typing.List[OptimizeVariationResult] = pydantic.Field()
+ """
+ One entry per variation.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_variation_comparison.py b/src/webflow/types/optimize_variation_comparison.py
new file mode 100644
index 00000000..f96ebec7
--- /dev/null
+++ b/src/webflow/types/optimize_variation_comparison.py
@@ -0,0 +1,53 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeVariationComparison(UniversalBaseModel):
+ """
+ How the variation compares with the base variation. Omitted for `personalize` optimizations.
+ """
+
+ is_baseline: typing_extensions.Annotated[
+ bool,
+ FieldMetadata(alias="isBaseline"),
+ pydantic.Field(alias="isBaseline", description="Whether this is the base variation."),
+ ]
+ is_winner: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isWinner"),
+ pydantic.Field(
+ alias="isWinner", description="Whether this is the winning variation. For `test` optimizations only."
+ ),
+ ] = None
+ lift: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ The variation's lift, as a fraction. For example, `0.19` means 19%. For `test` optimizations only.
+ """
+
+ statistical_significance: typing_extensions.Annotated[
+ typing.Optional[float],
+ FieldMetadata(alias="statisticalSignificance"),
+ pydantic.Field(
+ alias="statisticalSignificance",
+ description="The variation's [statistical significance](https://help.webflow.com/hc/en-us/articles/34167147351315-Variation-statistical-significance-overview), as a probability from 0 to 1. For `test` optimizations only.",
+ ),
+ ] = None
+ strength: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ The variation's [strength](https://help.webflow.com/hc/en-us/articles/33615967793427-Variation-strength-overview), as a probability from 0 to 1. For `aiOptimize` optimizations only.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_variation_result.py b/src/webflow/types/optimize_variation_result.py
new file mode 100644
index 00000000..3becd68d
--- /dev/null
+++ b/src/webflow/types/optimize_variation_result.py
@@ -0,0 +1,69 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .optimize_variation_comparison import OptimizeVariationComparison
+
+
+class OptimizeVariationResult(UniversalBaseModel):
+ """
+ One variation's results for the report's goal.
+ """
+
+ variation_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="variationId"), pydantic.Field(alias="variationId", description="The variation ID.")
+ ]
+ name: str = pydantic.Field()
+ """
+ The variation's name.
+ """
+
+ sessions: float = pydantic.Field()
+ """
+ The number of sessions that saw the variation.
+ """
+
+ users: float = pydantic.Field()
+ """
+ The number of users who saw the variation.
+ """
+
+ conversions: float = pydantic.Field()
+ """
+ The number of conversions.
+ """
+
+ conversion_rate: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="conversionRate"),
+ pydantic.Field(
+ alias="conversionRate", description="The conversion rate, as a fraction. For example, `0.19` means 19%."
+ ),
+ ]
+ value: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ The total value of the variation's conversions. For `value` goals only.
+ """
+
+ value_per_conversion: typing_extensions.Annotated[
+ typing.Optional[float],
+ FieldMetadata(alias="valuePerConversion"),
+ pydantic.Field(
+ alias="valuePerConversion",
+ description="The average conversion value. For `value` goals only, and omitted when `conversions` is 0.",
+ ),
+ ] = None
+ comparison: typing.Optional[OptimizeVariationComparison] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/optimize_webflow_site_resource.py b/src/webflow/types/optimize_webflow_site_resource.py
new file mode 100644
index 00000000..0b8ddd34
--- /dev/null
+++ b/src/webflow/types/optimize_webflow_site_resource.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class OptimizeWebflowSiteResource(UniversalBaseModel):
+ """
+ A Webflow site.
+ """
+
+ site_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="siteId"), pydantic.Field(alias="siteId", description="The Webflow site ID.")
+ ]
+ workspace_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="workspaceId"),
+ pydantic.Field(alias="workspaceId", description="The ID of the workspace the site belongs to."),
+ ] = None
+ display_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="displayName"),
+ pydantic.Field(alias="displayName", description="The site's display name."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/page_created_webhook_payload.py b/src/webflow/types/page_created_webhook_payload.py
index 4dd3385e..be0d1ca6 100644
--- a/src/webflow/types/page_created_webhook_payload.py
+++ b/src/webflow/types/page_created_webhook_payload.py
@@ -23,6 +23,26 @@ class PageCreatedWebhookPayload(UniversalBaseModel):
page_title: typing_extensions.Annotated[
typing.Optional[str], FieldMetadata(alias="pageTitle"), pydantic.Field(alias="pageTitle")
] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageName"),
+ pydantic.Field(alias="pageName", description="The display name of the page, or null if not set"),
+ ] = None
+ archived: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is archived
+ """
+
+ draft: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is a draft
+ """
+
+ is_branch: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isBranch"),
+ pydantic.Field(alias="isBranch", description="Whether the page belongs to a branch"),
+ ] = None
created_on: typing_extensions.Annotated[
typing.Optional[dt.datetime], FieldMetadata(alias="createdOn"), pydantic.Field(alias="createdOn")
] = None
diff --git a/src/webflow/types/page_deleted_webhook_payload.py b/src/webflow/types/page_deleted_webhook_payload.py
index e78f589b..f2df40e3 100644
--- a/src/webflow/types/page_deleted_webhook_payload.py
+++ b/src/webflow/types/page_deleted_webhook_payload.py
@@ -23,6 +23,26 @@ class PageDeletedWebhookPayload(UniversalBaseModel):
page_title: typing_extensions.Annotated[
typing.Optional[str], FieldMetadata(alias="pageTitle"), pydantic.Field(alias="pageTitle")
] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageName"),
+ pydantic.Field(alias="pageName", description="The display name of the page, or null if not set"),
+ ] = None
+ archived: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is archived
+ """
+
+ draft: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is a draft
+ """
+
+ is_branch: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isBranch"),
+ pydantic.Field(alias="isBranch", description="Whether the page belongs to a branch"),
+ ] = None
deleted_on: typing_extensions.Annotated[
typing.Optional[dt.datetime], FieldMetadata(alias="deletedOn"), pydantic.Field(alias="deletedOn")
] = None
diff --git a/src/webflow/types/page_flow_direction.py b/src/webflow/types/page_flow_direction.py
new file mode 100644
index 00000000..f25e5a6f
--- /dev/null
+++ b/src/webflow/types/page_flow_direction.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+PageFlowDirection = typing.Union[typing.Literal["previous", "next"], typing.Any]
diff --git a/src/webflow/types/page_flow_filter.py b/src/webflow/types/page_flow_filter.py
new file mode 100644
index 00000000..e1cbe361
--- /dev/null
+++ b/src/webflow/types/page_flow_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class PageFlowFilter(UniversalBaseModel):
+ """
+ Filter the page flow report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/page_flow_metric_scope.py b/src/webflow/types/page_flow_metric_scope.py
new file mode 100644
index 00000000..1bcb3e00
--- /dev/null
+++ b/src/webflow/types/page_flow_metric_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+PageFlowMetricScope = typing.Union[typing.Literal["session", "user", "pageview"], typing.Any]
diff --git a/src/webflow/types/page_flow_response.py b/src/webflow/types/page_flow_response.py
new file mode 100644
index 00000000..959831d8
--- /dev/null
+++ b/src/webflow/types/page_flow_response.py
@@ -0,0 +1,71 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_window import AnalyzeWindow
+from .page_flow_direction import PageFlowDirection
+from .page_flow_filter import PageFlowFilter
+from .page_flow_metric_scope import PageFlowMetricScope
+from .page_flow_row import PageFlowRow
+
+
+class PageFlowResponse(UniversalBaseModel):
+ """
+ Response payload for the page flow report.
+ """
+
+ report: typing.Literal["page_flow"] = pydantic.Field(default="page_flow")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ direction: PageFlowDirection
+ metric_scope: typing_extensions.Annotated[
+ PageFlowMetricScope, FieldMetadata(alias="metricScope"), pydantic.Field(alias="metricScope")
+ ]
+ page_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="Identifier of the anchor Webflow page."),
+ ]
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(
+ alias="collectionId",
+ description="Identifier of the CMS collection for the anchor template-page instance. Present with `itemSlug` when the request targeted a CMS page.",
+ ),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(
+ alias="itemSlug",
+ description="Slug of the CMS item for the anchor template-page instance. Present with `collectionId` when the request targeted a CMS page.",
+ ),
+ ] = None
+ limit: int = pydantic.Field()
+ """
+ The row cap applied to this response, including the default.
+ """
+
+ data: typing.List[PageFlowRow] = pydantic.Field()
+ """
+ Related pages ranked by `count`, descending. At most `limit` rows.
+ """
+
+ filter: typing.Optional[PageFlowFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/page_flow_row.py b/src/webflow/types/page_flow_row.py
new file mode 100644
index 00000000..e3c07495
--- /dev/null
+++ b/src/webflow/types/page_flow_row.py
@@ -0,0 +1,60 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class PageFlowRow(UniversalBaseModel):
+ """
+ A page viewed immediately before or after the anchor page, with its count for the requested `metricScope`.
+ """
+
+ page_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="Identifier of the related Webflow page."),
+ ]
+ title: str = pydantic.Field()
+ """
+ Display title for the related page, including the CMS item name when applicable.
+ """
+
+ page_name: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="pageName"),
+ pydantic.Field(alias="pageName", description="Name of the related Webflow page."),
+ ]
+ count: int = pydantic.Field()
+ """
+ Non-negative count for this related page, in units of the request's `metricScope`.
+ """
+
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(
+ alias="collectionId",
+ description="Identifier of the CMS collection this page belongs to. Present only for CMS-templated pages.",
+ ),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(
+ alias="itemSlug",
+ description="Slug of the CMS item this row represents. Present only for CMS-templated pages.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/page_metadata_updated_webhook_payload.py b/src/webflow/types/page_metadata_updated_webhook_payload.py
index d71cf47b..31283e0b 100644
--- a/src/webflow/types/page_metadata_updated_webhook_payload.py
+++ b/src/webflow/types/page_metadata_updated_webhook_payload.py
@@ -23,6 +23,26 @@ class PageMetadataUpdatedWebhookPayload(UniversalBaseModel):
page_title: typing_extensions.Annotated[
typing.Optional[str], FieldMetadata(alias="pageTitle"), pydantic.Field(alias="pageTitle")
] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageName"),
+ pydantic.Field(alias="pageName", description="The display name of the page, or null if not set"),
+ ] = None
+ archived: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is archived
+ """
+
+ draft: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Whether the page is a draft
+ """
+
+ is_branch: typing_extensions.Annotated[
+ typing.Optional[bool],
+ FieldMetadata(alias="isBranch"),
+ pydantic.Field(alias="isBranch", description="Whether the page belongs to a branch"),
+ ] = None
last_updated: typing_extensions.Annotated[
typing.Optional[dt.datetime], FieldMetadata(alias="lastUpdated"), pydantic.Field(alias="lastUpdated")
] = None
diff --git a/src/webflow/types/person_actor.py b/src/webflow/types/person_actor.py
new file mode 100644
index 00000000..00d5c398
--- /dev/null
+++ b/src/webflow/types/person_actor.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class PersonActor(UniversalBaseModel):
+ id: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The ID of the user who performed the action
+ """
+
+ email: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The email address of the user who performed the action
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/scroll_depth_data_point.py b/src/webflow/types/scroll_depth_data_point.py
new file mode 100644
index 00000000..7108f7d7
--- /dev/null
+++ b/src/webflow/types/scroll_depth_data_point.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class ScrollDepthDataPoint(UniversalBaseModel):
+ """
+ A scroll-depth breakpoint. `pageviewSharePercent` is the share of pageviews that scrolled at least `depthPixels` deep — not the percent of the page scrolled.
+ """
+
+ pageview_share_percent: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="pageviewSharePercent"),
+ pydantic.Field(
+ alias="pageviewSharePercent",
+ description="Share of page views (1–100) that scrolled at least `depthPixels` deep. The report currently returns breakpoints at 75, 50, and 25.",
+ ),
+ ]
+ depth_pixels: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="depthPixels"),
+ pydantic.Field(alias="depthPixels", description="Scroll depth from the top of the page, in CSS pixels."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/scroll_depth_filter.py b/src/webflow/types/scroll_depth_filter.py
new file mode 100644
index 00000000..e764ff24
--- /dev/null
+++ b/src/webflow/types/scroll_depth_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class ScrollDepthFilter(UniversalBaseModel):
+ """
+ Filter the scroll depth report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/scroll_depth_response.py b/src/webflow/types/scroll_depth_response.py
new file mode 100644
index 00000000..75d6dbfa
--- /dev/null
+++ b/src/webflow/types/scroll_depth_response.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .analyze_window import AnalyzeWindow
+from .scroll_depth_data_point import ScrollDepthDataPoint
+from .scroll_depth_filter import ScrollDepthFilter
+
+
+class ScrollDepthResponse(UniversalBaseModel):
+ """
+ Response payload for the scroll depth report.
+ """
+
+ report: typing.Literal["scroll_depth"] = pydantic.Field(default="scroll_depth")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ data: typing.List[ScrollDepthDataPoint] = pydantic.Field()
+ """
+ Scroll-depth breakpoints sorted from shallowest (highest share) to deepest (lowest share). The report currently returns up to three rows, for the 75%, 50%, and 25% breakpoints. Rows may be omitted when the underlying data is sparse.
+ """
+
+ filter: typing.Optional[ScrollDepthFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/setting_change.py b/src/webflow/types/setting_change.py
index c2de45a5..56d08766 100644
--- a/src/webflow/types/setting_change.py
+++ b/src/webflow/types/setting_change.py
@@ -6,7 +6,6 @@
import typing_extensions
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ..core.serialization import FieldMetadata
-from .workspace_audit_log_item_payload_setting_change_method import WorkspaceAuditLogItemPayloadSettingChangeMethod
class SettingChange(UniversalBaseModel):
@@ -15,7 +14,7 @@ class SettingChange(UniversalBaseModel):
typing.Optional[str], FieldMetadata(alias="previousValue"), pydantic.Field(alias="previousValue")
] = None
value: typing.Optional[str] = None
- method: typing.Optional[WorkspaceAuditLogItemPayloadSettingChangeMethod] = None
+ method: typing.Optional[typing.Literal["dashboard"]] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/site_activity_log_item.py b/src/webflow/types/site_activity_log_item.py
index cc07cef0..01e4d7b8 100644
--- a/src/webflow/types/site_activity_log_item.py
+++ b/src/webflow/types/site_activity_log_item.py
@@ -7,8 +7,10 @@
import typing_extensions
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ..core.serialization import FieldMetadata
+from .site_activity_log_item_actor_type import SiteActivityLogItemActorType
from .site_activity_log_item_event import SiteActivityLogItemEvent
from .site_activity_log_item_resource_operation import SiteActivityLogItemResourceOperation
+from .site_activity_log_item_source import SiteActivityLogItemSource
from .site_activity_log_item_user import SiteActivityLogItemUser
@@ -40,6 +42,35 @@ class SiteActivityLogItem(UniversalBaseModel):
typing.Optional[str], FieldMetadata(alias="previousValue"), pydantic.Field(alias="previousValue")
] = None
payload: typing.Optional[typing.Dict[str, typing.Any]] = None
+ source: typing.Optional[SiteActivityLogItemSource] = pydantic.Field(default=None)
+ """
+ The system that originated the event. `WEBFLOW_AI` for Webflow AI features, `WEBFLOW_MCP` for an external MCP server or Bridge App, `DESIGNER` for human writes from the Designer, and `SYSTEM` for automated Webflow processes such as backups or migrations. `null` for legacy events recorded before attribution was available.
+ """
+
+ actor_type: typing_extensions.Annotated[
+ typing.Optional[SiteActivityLogItemActorType],
+ FieldMetadata(alias="actorType"),
+ pydantic.Field(
+ alias="actorType",
+ description="The type of actor responsible for the event. `user` for a human who directly triggered or accepted the action, `agent` for a fully autonomous AI agent, `workflow` for a user-created workflow that ran autonomously, and `rule` for an autonomous rule that fired on a trigger. `null` for legacy events.",
+ ),
+ ] = None
+ actor_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="actorId"),
+ pydantic.Field(
+ alias="actorId",
+ description="Unique identifier of the actor that originated the event. `null` when not available.",
+ ),
+ ] = None
+ actor_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="actorName"),
+ pydantic.Field(
+ alias="actorName",
+ description="Display name of the actor that originated the event. `null` when not available.",
+ ),
+ ] = None
if IS_PYDANTIC_V2:
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
diff --git a/src/webflow/types/site_activity_log_item_actor_type.py b/src/webflow/types/site_activity_log_item_actor_type.py
new file mode 100644
index 00000000..1746b298
--- /dev/null
+++ b/src/webflow/types/site_activity_log_item_actor_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SiteActivityLogItemActorType = typing.Union[typing.Literal["user", "agent", "workflow", "rule"], typing.Any]
diff --git a/src/webflow/types/site_activity_log_item_source.py b/src/webflow/types/site_activity_log_item_source.py
new file mode 100644
index 00000000..6a704c00
--- /dev/null
+++ b/src/webflow/types/site_activity_log_item_source.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SiteActivityLogItemSource = typing.Union[typing.Literal["WEBFLOW_AI", "WEBFLOW_MCP", "DESIGNER", "SYSTEM"], typing.Any]
diff --git a/src/webflow/types/site_member.py b/src/webflow/types/site_member.py
new file mode 100644
index 00000000..86c2c565
--- /dev/null
+++ b/src/webflow/types/site_member.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .site_member_roles import SiteMemberRoles
+
+
+class SiteMember(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ Unique identifier for the Member
+ """
+
+ email: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Email address of the Member
+ """
+
+ first_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="firstName"),
+ pydantic.Field(alias="firstName", description="First name of the Member"),
+ ] = None
+ last_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="lastName"),
+ pydantic.Field(alias="lastName", description="Last name of the Member"),
+ ] = None
+ roles: SiteMemberRoles = pydantic.Field()
+ """
+ Roles the Member holds
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/site_member_roles.py b/src/webflow/types/site_member_roles.py
new file mode 100644
index 00000000..ba5aa672
--- /dev/null
+++ b/src/webflow/types/site_member_roles.py
@@ -0,0 +1,43 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .site_member_roles_site import SiteMemberRolesSite
+from .site_member_roles_workspace import SiteMemberRolesWorkspace
+
+
+class SiteMemberRoles(UniversalBaseModel):
+ """
+ Roles the Member holds
+ """
+
+ workspace: SiteMemberRolesWorkspace = pydantic.Field()
+ """
+ The Member's role in the Workspace that owns this Site
+ """
+
+ site: SiteMemberRolesSite = pydantic.Field()
+ """
+ The Member's Site Role on this Site
+ """
+
+ site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="siteRoleId"),
+ pydantic.Field(
+ alias="siteRoleId", description="Unique identifier for the custom Site Role, when `site` is `custom`"
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/site_member_roles_site.py b/src/webflow/types/site_member_roles_site.py
new file mode 100644
index 00000000..d08cce0a
--- /dev/null
+++ b/src/webflow/types/site_member_roles_site.py
@@ -0,0 +1,18 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SiteMemberRolesSite = typing.Union[
+ typing.Literal[
+ "site_manager",
+ "designer",
+ "branch_designer",
+ "limited_designer",
+ "marketer",
+ "content_editor",
+ "reviewer",
+ "viewer",
+ "custom",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/site_member_roles_workspace.py b/src/webflow/types/site_member_roles_workspace.py
new file mode 100644
index 00000000..c6bcd28d
--- /dev/null
+++ b/src/webflow/types/site_member_roles_workspace.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SiteMemberRolesWorkspace = typing.Union[
+ typing.Literal["owner", "admin", "member", "guest", "reviewer", "client"], typing.Any
+]
diff --git a/src/webflow/types/site_members_response.py b/src/webflow/types/site_members_response.py
new file mode 100644
index 00000000..10fcc272
--- /dev/null
+++ b/src/webflow/types/site_members_response.py
@@ -0,0 +1,29 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .site_member import SiteMember
+from .site_members_response_pagination import SiteMembersResponsePagination
+
+
+class SiteMembersResponse(UniversalBaseModel):
+ members: typing.List[SiteMember] = pydantic.Field()
+ """
+ List of Site Members
+ """
+
+ pagination: SiteMembersResponsePagination = pydantic.Field()
+ """
+ Pagination object
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/site_members_response_pagination.py b/src/webflow/types/site_members_response_pagination.py
new file mode 100644
index 00000000..db5f7795
--- /dev/null
+++ b/src/webflow/types/site_members_response_pagination.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class SiteMembersResponsePagination(UniversalBaseModel):
+ """
+ Pagination object
+ """
+
+ limit: int = pydantic.Field()
+ """
+ The limit used for pagination
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Cursor for the next page of results. `null` when there are no more results.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/site_publish_payload.py b/src/webflow/types/site_publish_payload.py
index 3e611453..2bcadfe5 100644
--- a/src/webflow/types/site_publish_payload.py
+++ b/src/webflow/types/site_publish_payload.py
@@ -7,6 +7,7 @@
import typing_extensions
from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ..core.serialization import FieldMetadata
+from .site_publish_payload_publish_scope import SitePublishPayloadPublishScope
class SitePublishPayload(UniversalBaseModel):
@@ -32,7 +33,17 @@ class SitePublishPayload(UniversalBaseModel):
published_by: typing_extensions.Annotated[
typing.Optional[typing.Dict[str, typing.Any]],
FieldMetadata(alias="publishedBy"),
- pydantic.Field(alias="publishedBy", description="The name andID of the user who published the site"),
+ pydantic.Field(alias="publishedBy", description="The name and ID of the user who published the site"),
+ ] = None
+ publish_scope: typing_extensions.Annotated[
+ typing.Optional[SitePublishPayloadPublishScope],
+ FieldMetadata(alias="publishScope"),
+ pydantic.Field(alias="publishScope", description="Whether the entire site or an individual page was published"),
+ ] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="The ID of the page that was published"),
] = None
if IS_PYDANTIC_V2:
diff --git a/src/webflow/types/site_publish_payload_publish_scope.py b/src/webflow/types/site_publish_payload_publish_scope.py
new file mode 100644
index 00000000..029f2082
--- /dev/null
+++ b/src/webflow/types/site_publish_payload_publish_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SitePublishPayloadPublishScope = typing.Union[typing.Literal["page", "site"], typing.Any]
diff --git a/src/webflow/types/time_on_page_data_point.py b/src/webflow/types/time_on_page_data_point.py
new file mode 100644
index 00000000..5fee487c
--- /dev/null
+++ b/src/webflow/types/time_on_page_data_point.py
@@ -0,0 +1,38 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class TimeOnPageDataPoint(UniversalBaseModel):
+ """
+ A single average time on page data point. When `timeseries` is omitted, the response holds one aggregate point covering the whole window; otherwise it returns one point per requested bucket.
+ """
+
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ Start of the bucket, in ISO 8601 / RFC 3339 format. For an aggregate result, this is the start of the requested window.
+ """
+
+ average_seconds: typing_extensions.Annotated[
+ float,
+ FieldMetadata(alias="averageSeconds"),
+ pydantic.Field(
+ alias="averageSeconds",
+ description="Average time on page for this bucket, in seconds, in units of the request's `metricScope`.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/time_on_page_filter.py b/src/webflow/types/time_on_page_filter.py
new file mode 100644
index 00000000..d1eee7e3
--- /dev/null
+++ b/src/webflow/types/time_on_page_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class TimeOnPageFilter(UniversalBaseModel):
+ """
+ Filter the time on page report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/time_on_page_granularity_period.py b/src/webflow/types/time_on_page_granularity_period.py
new file mode 100644
index 00000000..f0337bff
--- /dev/null
+++ b/src/webflow/types/time_on_page_granularity_period.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TimeOnPageGranularityPeriod = typing.Union[typing.Literal["day", "week"], typing.Any]
diff --git a/src/webflow/types/time_on_page_metric_scope.py b/src/webflow/types/time_on_page_metric_scope.py
new file mode 100644
index 00000000..a65f864d
--- /dev/null
+++ b/src/webflow/types/time_on_page_metric_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TimeOnPageMetricScope = typing.Union[typing.Literal["session", "user", "pageview"], typing.Any]
diff --git a/src/webflow/types/time_on_page_response.py b/src/webflow/types/time_on_page_response.py
new file mode 100644
index 00000000..9384104a
--- /dev/null
+++ b/src/webflow/types/time_on_page_response.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_time_on_page_bucketing import AnalyzeTimeOnPageBucketing
+from .analyze_window import AnalyzeWindow
+from .time_on_page_data_point import TimeOnPageDataPoint
+from .time_on_page_filter import TimeOnPageFilter
+from .time_on_page_metric_scope import TimeOnPageMetricScope
+
+
+class TimeOnPageResponse(UniversalBaseModel):
+ """
+ Response payload for the time on page report.
+ """
+
+ report: typing.Literal["time_on_page"] = pydantic.Field(default="time_on_page")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ metric_scope: typing_extensions.Annotated[
+ TimeOnPageMetricScope, FieldMetadata(alias="metricScope"), pydantic.Field(alias="metricScope")
+ ]
+ bucketing: typing.Optional[AnalyzeTimeOnPageBucketing] = None
+ data: typing.List[TimeOnPageDataPoint] = pydantic.Field()
+ """
+ Average time on page over the requested window — one aggregate point when `timeseries` is omitted, otherwise one point per bucket.
+ """
+
+ filter: typing.Optional[TimeOnPageFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/time_on_page_timeseries_query.py b/src/webflow/types/time_on_page_timeseries_query.py
new file mode 100644
index 00000000..4930c08f
--- /dev/null
+++ b/src/webflow/types/time_on_page_timeseries_query.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_bucket_time_zone import AnalyzeBucketTimeZone
+from .time_on_page_granularity_period import TimeOnPageGranularityPeriod
+
+
+class TimeOnPageTimeseriesQuery(UniversalBaseModel):
+ """
+ Options that opt the time on page report into bucketed timeseries data.
+ """
+
+ granularity_period: typing_extensions.Annotated[
+ TimeOnPageGranularityPeriod, FieldMetadata(alias="granularityPeriod"), pydantic.Field(alias="granularityPeriod")
+ ]
+ bucket_time_zone: typing_extensions.Annotated[
+ AnalyzeBucketTimeZone, FieldMetadata(alias="bucketTimeZone"), pydantic.Field(alias="bucketTimeZone")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/token_actor.py b/src/webflow/types/token_actor.py
new file mode 100644
index 00000000..eaf9cdc1
--- /dev/null
+++ b/src/webflow/types/token_actor.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class TokenActor(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ The ID of the API token's authorization. This is not a user ID.
+ """
+
+ name: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ The name of the API token, if it has one
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_dimensions_dimension.py b/src/webflow/types/top_dimensions_dimension.py
new file mode 100644
index 00000000..513e9d8d
--- /dev/null
+++ b/src/webflow/types/top_dimensions_dimension.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TopDimensionsDimension = typing.Union[
+ typing.Literal[
+ "country",
+ "region",
+ "deviceType",
+ "os",
+ "browser",
+ "language",
+ "locale",
+ "referrer",
+ "trafficSource",
+ "utmCampaign",
+ "utmContent",
+ "utmMedium",
+ "utmSource",
+ "utmTerm",
+ "audienceIds",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/top_dimensions_filter.py b/src/webflow/types/top_dimensions_filter.py
new file mode 100644
index 00000000..80f7c364
--- /dev/null
+++ b/src/webflow/types/top_dimensions_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class TopDimensionsFilter(UniversalBaseModel):
+ """
+ Filter the top dimensions report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_dimensions_metric_scope.py b/src/webflow/types/top_dimensions_metric_scope.py
new file mode 100644
index 00000000..b47d7868
--- /dev/null
+++ b/src/webflow/types/top_dimensions_metric_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TopDimensionsMetricScope = typing.Union[typing.Literal["session", "user"], typing.Any]
diff --git a/src/webflow/types/top_dimensions_response.py b/src/webflow/types/top_dimensions_response.py
new file mode 100644
index 00000000..f07829bf
--- /dev/null
+++ b/src/webflow/types/top_dimensions_response.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_window import AnalyzeWindow
+from .top_dimensions_dimension import TopDimensionsDimension
+from .top_dimensions_filter import TopDimensionsFilter
+from .top_dimensions_metric_scope import TopDimensionsMetricScope
+from .top_dimensions_row import TopDimensionsRow
+
+
+class TopDimensionsResponse(UniversalBaseModel):
+ """
+ Response payload for the top dimensions report.
+ """
+
+ report: typing.Literal["top_dimensions"] = pydantic.Field(default="top_dimensions")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ dimension: TopDimensionsDimension
+ metric_scope: typing_extensions.Annotated[
+ TopDimensionsMetricScope, FieldMetadata(alias="metricScope"), pydantic.Field(alias="metricScope")
+ ]
+ limit: int = pydantic.Field()
+ """
+ The row cap that was applied to this response (echoes the resolved request value, including the default).
+ """
+
+ data: typing.List[TopDimensionsRow] = pydantic.Field()
+ """
+ Dimension values ranked by `count`, descending. At most `limit` rows.
+ """
+
+ filter: typing.Optional[TopDimensionsFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_dimensions_row.py b/src/webflow/types/top_dimensions_row.py
new file mode 100644
index 00000000..df568744
--- /dev/null
+++ b/src/webflow/types/top_dimensions_row.py
@@ -0,0 +1,41 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class TopDimensionsRow(UniversalBaseModel):
+ """
+ A single value within the chosen dimension, with its count for the requested `metricScope`.
+ """
+
+ attribute_key: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="attributeKey"),
+ pydantic.Field(
+ alias="attributeKey",
+ description="Identifier for the dimension value. Pass this back into a `filter` on any Analyze report to scope that report to this value.",
+ ),
+ ]
+ name: str = pydantic.Field()
+ """
+ Display label for the dimension value. For many dimensions this matches `attributeKey`; for others (such as `region`, `trafficSource`, and `audienceIds`) it's a human-readable label.
+ """
+
+ count: int = pydantic.Field()
+ """
+ Non-negative count for this dimension value, in units of the request's `metricScope`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_cms_context_entry.py b/src/webflow/types/top_events_cms_context_entry.py
new file mode 100644
index 00000000..d5edc7a4
--- /dev/null
+++ b/src/webflow/types/top_events_cms_context_entry.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class TopEventsCmsContextEntry(UniversalBaseModel):
+ """
+ CMS collection-list rendering context for a top event row.
+ """
+
+ collection_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId", description="Identifier of the CMS collection."),
+ ]
+ item_id: typing_extensions.Annotated[
+ str, FieldMetadata(alias="itemId"), pydantic.Field(alias="itemId", description="Identifier of the CMS item.")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_component_context_entry.py b/src/webflow/types/top_events_component_context_entry.py
new file mode 100644
index 00000000..dff2b157
--- /dev/null
+++ b/src/webflow/types/top_events_component_context_entry.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class TopEventsComponentContextEntry(UniversalBaseModel):
+ """
+ Component rendering context for a top event row.
+ """
+
+ component_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="componentId"),
+ pydantic.Field(alias="componentId", description="Identifier of the component definition."),
+ ]
+ instance_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="instanceId"),
+ pydantic.Field(alias="instanceId", description="Identifier of the component instance."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_filter.py b/src/webflow/types/top_events_filter.py
new file mode 100644
index 00000000..9ef57775
--- /dev/null
+++ b/src/webflow/types/top_events_filter.py
@@ -0,0 +1,85 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class TopEventsFilter(UniversalBaseModel):
+ """
+ Filter the top events report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_response.py b/src/webflow/types/top_events_response.py
new file mode 100644
index 00000000..591e8f24
--- /dev/null
+++ b/src/webflow/types/top_events_response.py
@@ -0,0 +1,44 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .analyze_daily_bucketing import AnalyzeDailyBucketing
+from .analyze_window import AnalyzeWindow
+from .top_events_filter import TopEventsFilter
+from .top_events_row import TopEventsRow
+
+
+class TopEventsResponse(UniversalBaseModel):
+ """
+ Response payload for the top events report.
+ """
+
+ report: typing.Literal["top_events"] = pydantic.Field(default="top_events")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ limit: int = pydantic.Field()
+ """
+ The row cap that was applied to this response (echoes the resolved request value, including the default).
+ """
+
+ bucketing: typing.Optional[AnalyzeDailyBucketing] = None
+ data: typing.List[TopEventsRow] = pydantic.Field()
+ """
+ Events ranked by `count`, descending. At most `limit` rows.
+ """
+
+ filter: typing.Optional[TopEventsFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_row.py b/src/webflow/types/top_events_row.py
new file mode 100644
index 00000000..a029d902
--- /dev/null
+++ b/src/webflow/types/top_events_row.py
@@ -0,0 +1,93 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .top_events_cms_context_entry import TopEventsCmsContextEntry
+from .top_events_component_context_entry import TopEventsComponentContextEntry
+from .top_events_timeseries_point import TopEventsTimeseriesPoint
+
+
+class TopEventsRow(UniversalBaseModel):
+ """
+ A single event in the ranked response, with its event count over the requested window. Events are counted individually, not rolled up into sessions, users, or page views, so this report has no `metricScope` — `count` is always the number of times the event fired.
+ """
+
+ event_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="eventId"),
+ pydantic.Field(alias="eventId", description="Opaque identifier of the event."),
+ ]
+ name: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Display name of the event. Omitted when the event has no name (customer-defined events may leave it blank).
+ """
+
+ page_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(
+ alias="pageId",
+ description="Identifier of the Webflow page the event is associated with. Omitted when the upstream has no page context for the event.",
+ ),
+ ] = None
+ page_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="pageName"),
+ pydantic.Field(
+ alias="pageName", description="Display name of the associated page. Omitted when the page label is unknown."
+ ),
+ ] = None
+ count: int = pydantic.Field()
+ """
+ Number of times the event fired during the requested window.
+ """
+
+ component_context: typing_extensions.Annotated[
+ typing.Optional[typing.List[TopEventsComponentContextEntry]],
+ FieldMetadata(alias="componentContext"),
+ pydantic.Field(
+ alias="componentContext",
+ description="Component rendering context for the event. Omitted when the event is not rendered inside a component.",
+ ),
+ ] = None
+ cms_context: typing_extensions.Annotated[
+ typing.Optional[typing.List[TopEventsCmsContextEntry]],
+ FieldMetadata(alias="cmsContext"),
+ pydantic.Field(
+ alias="cmsContext",
+ description="CMS collection-list rendering context for the event. Omitted when the event is not rendered inside a CMS collection list.",
+ ),
+ ] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(
+ alias="collectionId",
+ description="Identifier of the CMS collection the associated page belongs to. Present only for events on CMS-templated pages.",
+ ),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(
+ alias="itemSlug",
+ description="Slug of the CMS item the associated page represents. Present only for events on CMS-templated pages.",
+ ),
+ ] = None
+ timeseries: typing.Optional[typing.List[TopEventsTimeseriesPoint]] = pydantic.Field(default=None)
+ """
+ Daily count timeseries for this event over the requested window. Returned when `timeseries` is requested.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_events_timeseries_point.py b/src/webflow/types/top_events_timeseries_point.py
new file mode 100644
index 00000000..251dc3e5
--- /dev/null
+++ b/src/webflow/types/top_events_timeseries_point.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class TopEventsTimeseriesPoint(UniversalBaseModel):
+ """
+ A single **daily** bucket in a per-event timeseries. Returned only when `timeseries` is requested.
+
+ Buckets are aligned to local day boundaries in the response's `bucketing.bucketTimeZone`; the `timestamp` is the UTC instant for that local bucket start. A row's `timeseries` contains daily buckets for the requested window. When `timeseries` is requested but the event did not occur in the window, `timeseries` is an empty array — distinguishing "asked, none" from "didn't ask".
+ """
+
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ Start of the daily bucket, in ISO 8601 / RFC 3339 format.
+ """
+
+ count: int = pydantic.Field()
+ """
+ Number of times the event occurred on this day.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_pages_filter.py b/src/webflow/types/top_pages_filter.py
new file mode 100644
index 00000000..13df95e0
--- /dev/null
+++ b/src/webflow/types/top_pages_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class TopPagesFilter(UniversalBaseModel):
+ """
+ Filter the top pages report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_pages_response.py b/src/webflow/types/top_pages_response.py
new file mode 100644
index 00000000..550c7b52
--- /dev/null
+++ b/src/webflow/types/top_pages_response.py
@@ -0,0 +1,48 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_daily_bucketing import AnalyzeDailyBucketing
+from .analyze_window import AnalyzeWindow
+from .top_pages_filter import TopPagesFilter
+from .top_pages_row import TopPagesRow
+from .top_pages_sort_by import TopPagesSortBy
+
+
+class TopPagesResponse(UniversalBaseModel):
+ """
+ Response payload for the top pages report.
+ """
+
+ report: typing.Literal["top_pages"] = pydantic.Field(default="top_pages")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ sort_by: typing_extensions.Annotated[TopPagesSortBy, FieldMetadata(alias="sortBy"), pydantic.Field(alias="sortBy")]
+ limit: int = pydantic.Field()
+ """
+ The row cap that was applied to this response (echoes the resolved request value, including the default).
+ """
+
+ bucketing: typing.Optional[AnalyzeDailyBucketing] = None
+ data: typing.List[TopPagesRow] = pydantic.Field()
+ """
+ Pages ranked by `sortBy`, descending. At most `limit` rows.
+ """
+
+ filter: typing.Optional[TopPagesFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_pages_row.py b/src/webflow/types/top_pages_row.py
new file mode 100644
index 00000000..b22ac652
--- /dev/null
+++ b/src/webflow/types/top_pages_row.py
@@ -0,0 +1,76 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .top_pages_timeseries_point import TopPagesTimeseriesPoint
+
+
+class TopPagesRow(UniversalBaseModel):
+ """
+ A single page in the ranked response. Every row carries all three scope counts (`sessionCount`, `userCount`, `pageviewCount`); `sortBy` governs only the row ordering.
+ """
+
+ page_id: typing_extensions.Annotated[
+ str,
+ FieldMetadata(alias="pageId"),
+ pydantic.Field(alias="pageId", description="Identifier of the Webflow page."),
+ ]
+ title: str = pydantic.Field()
+ """
+ Display title for the page. Resolves to the page's CMS item name when applicable, otherwise the page label. Falls back to `pageId` when no title is available.
+ """
+
+ session_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="sessionCount"),
+ pydantic.Field(
+ alias="sessionCount", description="Sessions attributed to this page during the requested window."
+ ),
+ ]
+ user_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="userCount"),
+ pydantic.Field(
+ alias="userCount", description="Unique users that visited this page during the requested window."
+ ),
+ ]
+ pageview_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="pageviewCount"),
+ pydantic.Field(
+ alias="pageviewCount", description="Page views recorded for this page during the requested window."
+ ),
+ ]
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(
+ alias="collectionId",
+ description="Identifier of the CMS collection this page belongs to. Present only for CMS-templated pages.",
+ ),
+ ] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="itemSlug"),
+ pydantic.Field(
+ alias="itemSlug",
+ description="Slug of the CMS item this row represents. Present only for CMS-templated pages.",
+ ),
+ ] = None
+ timeseries: typing.Optional[typing.List[TopPagesTimeseriesPoint]] = pydantic.Field(default=None)
+ """
+ Daily page view timeseries for this page over the requested window. Returned when `timeseries` is requested.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/top_pages_sort_by.py b/src/webflow/types/top_pages_sort_by.py
new file mode 100644
index 00000000..468ee6cd
--- /dev/null
+++ b/src/webflow/types/top_pages_sort_by.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TopPagesSortBy = typing.Union[typing.Literal["session", "user", "pageview"], typing.Any]
diff --git a/src/webflow/types/top_pages_timeseries_point.py b/src/webflow/types/top_pages_timeseries_point.py
new file mode 100644
index 00000000..85b2e531
--- /dev/null
+++ b/src/webflow/types/top_pages_timeseries_point.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class TopPagesTimeseriesPoint(UniversalBaseModel):
+ """
+ A single **daily** bucket in a per-page timeseries. Returned only when `timeseries` is requested.
+
+ Buckets are aligned to local day boundaries in the response's `bucketing.bucketTimeZone`; the `timestamp` is the UTC instant for that local bucket start. A row's `timeseries` contains daily buckets for the requested window.
+
+ Bucket counts are always page views, regardless of the request's `sortBy`. Row-level `sessionCount` / `userCount` / `pageviewCount` reflect `sortBy`.
+ """
+
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ Start of the daily bucket, in ISO 8601 / RFC 3339 format.
+ """
+
+ pageview_count: typing_extensions.Annotated[
+ int,
+ FieldMetadata(alias="pageviewCount"),
+ pydantic.Field(alias="pageviewCount", description="Page views recorded for this day."),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/traffic_data_point.py b/src/webflow/types/traffic_data_point.py
new file mode 100644
index 00000000..b7d6c9c7
--- /dev/null
+++ b/src/webflow/types/traffic_data_point.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class TrafficDataPoint(UniversalBaseModel):
+ """
+ A single daily bucket in a traffic time series. Buckets are aligned to local day boundaries in the response's `bucketing.bucketTimeZone`; the `timestamp` is the UTC instant for that local bucket start.
+ """
+
+ timestamp: dt.datetime = pydantic.Field()
+ """
+ Start of the daily bucket, in ISO 8601 / RFC 3339 format.
+ """
+
+ count: int = pydantic.Field()
+ """
+ Non-negative count for this day, in units of the request's `metricScope`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/traffic_filter.py b/src/webflow/types/traffic_filter.py
new file mode 100644
index 00000000..5a48264c
--- /dev/null
+++ b/src/webflow/types/traffic_filter.py
@@ -0,0 +1,114 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_filter_operators import AnalyzeFilterOperators
+
+
+class TrafficFilter(UniversalBaseModel):
+ """
+ Filter the traffic report by dimension. Each property is an optional set of `AnalyzeFilterOperators` (`eq`, `in`, `ne`, `nin`) applied to the named dimension. Filter a given dimension in one place — either inside `filter` or as a top-level query parameter.
+ """
+
+ audience_ids: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="audienceIds"), pydantic.Field(alias="audienceIds")
+ ] = None
+ browser: typing.Optional[AnalyzeFilterOperators] = None
+ collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="collectionId"),
+ pydantic.Field(alias="collectionId"),
+ ] = None
+ country: typing.Optional[AnalyzeFilterOperators] = None
+ day_of_week: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="dayOfWeek"), pydantic.Field(alias="dayOfWeek")
+ ] = None
+ device_brand: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceBrand"), pydantic.Field(alias="deviceBrand")
+ ] = None
+ device_type: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="deviceType"), pydantic.Field(alias="deviceType")
+ ] = None
+ domain: typing.Optional[AnalyzeFilterOperators] = None
+ item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="itemSlug"), pydantic.Field(alias="itemSlug")
+ ] = None
+ language: typing.Optional[AnalyzeFilterOperators] = None
+ locale: typing.Optional[AnalyzeFilterOperators] = None
+ next_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextCollectionId"),
+ pydantic.Field(alias="nextCollectionId"),
+ ] = None
+ next_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="nextItemSlug"),
+ pydantic.Field(alias="nextItemSlug"),
+ ] = None
+ next_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="nextPageId"), pydantic.Field(alias="nextPageId")
+ ] = None
+ os: typing.Optional[AnalyzeFilterOperators] = None
+ page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pageId"), pydantic.Field(alias="pageId")
+ ] = None
+ page_path: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="pagePath"), pydantic.Field(alias="pagePath")
+ ] = None
+ previous_collection_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousCollectionId"),
+ pydantic.Field(alias="previousCollectionId"),
+ ] = None
+ previous_item_slug: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousItemSlug"),
+ pydantic.Field(alias="previousItemSlug"),
+ ] = None
+ previous_page_id: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="previousPageId"),
+ pydantic.Field(alias="previousPageId"),
+ ] = None
+ referrer: typing.Optional[AnalyzeFilterOperators] = None
+ region: typing.Optional[AnalyzeFilterOperators] = None
+ time_of_day: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="timeOfDay"), pydantic.Field(alias="timeOfDay")
+ ] = None
+ timezone: typing.Optional[AnalyzeFilterOperators] = None
+ traffic_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators],
+ FieldMetadata(alias="trafficSource"),
+ pydantic.Field(alias="trafficSource"),
+ ] = None
+ utm_campaign: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmCampaign"), pydantic.Field(alias="utmCampaign")
+ ] = None
+ utm_content: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmContent"), pydantic.Field(alias="utmContent")
+ ] = None
+ utm_medium: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmMedium"), pydantic.Field(alias="utmMedium")
+ ] = None
+ utm_source: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmSource"), pydantic.Field(alias="utmSource")
+ ] = None
+ utm_term: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="utmTerm"), pydantic.Field(alias="utmTerm")
+ ] = None
+ visit_status: typing_extensions.Annotated[
+ typing.Optional[AnalyzeFilterOperators], FieldMetadata(alias="visitStatus"), pydantic.Field(alias="visitStatus")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/traffic_metric_scope.py b/src/webflow/types/traffic_metric_scope.py
new file mode 100644
index 00000000..7a54a55d
--- /dev/null
+++ b/src/webflow/types/traffic_metric_scope.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+TrafficMetricScope = typing.Union[typing.Literal["session", "user", "pageview"], typing.Any]
diff --git a/src/webflow/types/traffic_response.py b/src/webflow/types/traffic_response.py
new file mode 100644
index 00000000..e89d1adc
--- /dev/null
+++ b/src/webflow/types/traffic_response.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .analyze_daily_bucketing import AnalyzeDailyBucketing
+from .analyze_window import AnalyzeWindow
+from .traffic_data_point import TrafficDataPoint
+from .traffic_filter import TrafficFilter
+from .traffic_metric_scope import TrafficMetricScope
+
+
+class TrafficResponse(UniversalBaseModel):
+ """
+ Response payload for the traffic report.
+ """
+
+ report: typing.Literal["traffic"] = pydantic.Field(default="traffic")
+ """
+ Discriminator identifying the report type.
+ """
+
+ window: AnalyzeWindow
+ metric_scope: typing_extensions.Annotated[
+ TrafficMetricScope, FieldMetadata(alias="metricScope"), pydantic.Field(alias="metricScope")
+ ]
+ bucketing: AnalyzeDailyBucketing
+ data: typing.List[TrafficDataPoint] = pydantic.Field()
+ """
+ Time-ordered series of data points covering the requested window.
+ """
+
+ filter: typing.Optional[TrafficFilter] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/trigger_type.py b/src/webflow/types/trigger_type.py
index 5b9e8216..9aa41ec6 100644
--- a/src/webflow/types/trigger_type.py
+++ b/src/webflow/types/trigger_type.py
@@ -18,6 +18,9 @@
"collection_item_published",
"collection_item_unpublished",
"comment_created",
+ "branch_created",
+ "branch_merged",
+ "branch_deleted",
],
typing.Any,
]
diff --git a/src/webflow/types/update_site_member_role_request_body.py b/src/webflow/types/update_site_member_role_request_body.py
new file mode 100644
index 00000000..c3965f74
--- /dev/null
+++ b/src/webflow/types/update_site_member_role_request_body.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .update_site_member_role_request_body_site_role import UpdateSiteMemberRoleRequestBodySiteRole
+
+
+class UpdateSiteMemberRoleRequestBody(UniversalBaseModel):
+ site_role: typing_extensions.Annotated[
+ UpdateSiteMemberRoleRequestBodySiteRole,
+ FieldMetadata(alias="siteRole"),
+ pydantic.Field(alias="siteRole", description="The Site Role to assign"),
+ ]
+ site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="siteRoleId"),
+ pydantic.Field(
+ alias="siteRoleId",
+ description="ID of the custom Site Role to assign. Required when `siteRole` is `custom`, and rejected with any other `siteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/update_site_member_role_request_body_site_role.py b/src/webflow/types/update_site_member_role_request_body_site_role.py
new file mode 100644
index 00000000..d8946f85
--- /dev/null
+++ b/src/webflow/types/update_site_member_role_request_body_site_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+UpdateSiteMemberRoleRequestBodySiteRole = typing.Union[
+ typing.Literal["site_manager", "designer", "marketer", "content_editor", "reviewer", "custom"], typing.Any
+]
diff --git a/src/webflow/types/with_default_site_role.py b/src/webflow/types/with_default_site_role.py
new file mode 100644
index 00000000..0adae856
--- /dev/null
+++ b/src/webflow/types/with_default_site_role.py
@@ -0,0 +1,48 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .invite_workspace_member_request_body_default_site_role_default_site_role import (
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole,
+)
+from .invite_workspace_member_request_body_default_site_role_workspace_role import (
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole,
+)
+
+
+class WithDefaultSiteRole(UniversalBaseModel):
+ email: str
+ workspace_role: typing_extensions.Annotated[
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleWorkspaceRole,
+ FieldMetadata(alias="workspaceRole"),
+ pydantic.Field(alias="workspaceRole", description="The Workspace Role to invite the person as"),
+ ]
+ default_site_role: typing_extensions.Annotated[
+ InviteWorkspaceMemberRequestBodyDefaultSiteRoleDefaultSiteRole,
+ FieldMetadata(alias="defaultSiteRole"),
+ pydantic.Field(
+ alias="defaultSiteRole",
+ description="The Site Role to grant across every Site the Workspace default extends to",
+ ),
+ ]
+ default_site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="defaultSiteRoleId"),
+ pydantic.Field(
+ alias="defaultSiteRoleId",
+ description="ID of the custom Site Role named by `defaultSiteRole`. Requires `defaultSiteRole` to be `custom`, and rejected with any other `defaultSiteRole`. Rejected if the id doesn't name a custom Site Role belonging to this workspace.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/with_site_access.py b/src/webflow/types/with_site_access.py
new file mode 100644
index 00000000..44daa204
--- /dev/null
+++ b/src/webflow/types/with_site_access.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .invite_workspace_member_request_body_one_site_access_item import InviteWorkspaceMemberRequestBodyOneSiteAccessItem
+from .invite_workspace_member_request_body_one_workspace_role import InviteWorkspaceMemberRequestBodyOneWorkspaceRole
+
+
+class WithSiteAccess(UniversalBaseModel):
+ email: str
+ workspace_role: typing_extensions.Annotated[
+ InviteWorkspaceMemberRequestBodyOneWorkspaceRole,
+ FieldMetadata(alias="workspaceRole"),
+ pydantic.Field(alias="workspaceRole", description="The Workspace Role to invite the person as"),
+ ]
+ site_access: typing_extensions.Annotated[
+ typing.List[InviteWorkspaceMemberRequestBodyOneSiteAccessItem],
+ FieldMetadata(alias="siteAccess"),
+ pydantic.Field(
+ alias="siteAccess",
+ description="Sites to grant on acceptance, instead of one Workspace-wide default. Every entry must use the same `siteRole` (and `siteRoleId`, for `custom`) — a pending invite stores one Site Role for all of its Sites.",
+ ),
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace.py b/src/webflow/types/workspace.py
new file mode 100644
index 00000000..112ed226
--- /dev/null
+++ b/src/webflow/types/workspace.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class Workspace(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ Unique identifier for the workspace
+ """
+
+ name: str = pydantic.Field()
+ """
+ Name of the workspace
+ """
+
+ slug: str = pydantic.Field()
+ """
+ URL slug of the workspace
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_audit_log_actor.py b/src/webflow/types/workspace_audit_log_actor.py
new file mode 100644
index 00000000..8d86c925
--- /dev/null
+++ b/src/webflow/types/workspace_audit_log_actor.py
@@ -0,0 +1,52 @@
+# This file was auto-generated by Fern from our API Definition.
+
+from __future__ import annotations
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class WorkspaceAuditLogActor_Person(UniversalBaseModel):
+ """
+ The user or API token that performed the action. Use `type` to tell them apart.
+ """
+
+ type: typing.Literal["person"] = "person"
+ id: typing.Optional[str] = None
+ email: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+class WorkspaceAuditLogActor_Token(UniversalBaseModel):
+ """
+ The user or API token that performed the action. Use `type` to tell them apart.
+ """
+
+ type: typing.Literal["token"] = "token"
+ id: str
+ name: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
+
+
+WorkspaceAuditLogActor = typing_extensions.Annotated[
+ typing.Union[WorkspaceAuditLogActor_Person, WorkspaceAuditLogActor_Token], pydantic.Field(discriminator="type")
+]
diff --git a/src/webflow/types/workspace_audit_log_item.py b/src/webflow/types/workspace_audit_log_item.py
index 7d3a3c57..4f5ef512 100644
--- a/src/webflow/types/workspace_audit_log_item.py
+++ b/src/webflow/types/workspace_audit_log_item.py
@@ -16,7 +16,7 @@
from .site_membership_audit_log_item_event_sub_type import SiteMembershipAuditLogItemEventSubType
from .user_access import UserAccess
from .user_access_audit_log_item_event_sub_type import UserAccessAuditLogItemEventSubType
-from .workspace_audit_log_item_actor import WorkspaceAuditLogItemActor
+from .workspace_audit_log_actor import WorkspaceAuditLogActor
from .workspace_audit_log_item_workspace import WorkspaceAuditLogItemWorkspace
from .workspace_invitation import WorkspaceInvitation
from .workspace_invitation_audit_log_item_event_sub_type import WorkspaceInvitationAuditLogItemEventSubType
@@ -26,7 +26,7 @@
class Base(UniversalBaseModel):
timestamp: typing.Optional[dt.datetime] = None
- actor: typing.Optional[WorkspaceAuditLogItemActor] = None
+ actor: typing.Optional[WorkspaceAuditLogActor] = None
workspace: typing.Optional[WorkspaceAuditLogItemWorkspace] = None
if IS_PYDANTIC_V2:
diff --git a/src/webflow/types/workspace_audit_log_item_payload_setting_change_method.py b/src/webflow/types/workspace_audit_log_item_payload_setting_change_method.py
deleted file mode 100644
index 4b91759e..00000000
--- a/src/webflow/types/workspace_audit_log_item_payload_setting_change_method.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# This file was auto-generated by Fern from our API Definition.
-
-import typing
-
-WorkspaceAuditLogItemPayloadSettingChangeMethod = typing.Union[typing.Literal["dashboard", "admin"], typing.Any]
diff --git a/src/webflow/types/workspace_audit_log_item_payload_site_membership_method.py b/src/webflow/types/workspace_audit_log_item_payload_site_membership_method.py
index a13fbae4..36d1f193 100644
--- a/src/webflow/types/workspace_audit_log_item_payload_site_membership_method.py
+++ b/src/webflow/types/workspace_audit_log_item_payload_site_membership_method.py
@@ -3,5 +3,5 @@
import typing
WorkspaceAuditLogItemPayloadSiteMembershipMethod = typing.Union[
- typing.Literal["sso", "invite", "scim", "dashboard", "admin", "access_request"], typing.Any
+ typing.Literal["invite", "scim", "dashboard", "admin", "access_request", "api"], typing.Any
]
diff --git a/src/webflow/types/workspace_audit_log_item_payload_workspace_invitation_method.py b/src/webflow/types/workspace_audit_log_item_payload_workspace_invitation_method.py
index 79a30421..6abda636 100644
--- a/src/webflow/types/workspace_audit_log_item_payload_workspace_invitation_method.py
+++ b/src/webflow/types/workspace_audit_log_item_payload_workspace_invitation_method.py
@@ -3,5 +3,5 @@
import typing
WorkspaceAuditLogItemPayloadWorkspaceInvitationMethod = typing.Union[
- typing.Literal["sso", "dashboard", "admin"], typing.Any
+ typing.Literal["sso", "dashboard", "admin", "api", "designer", "scim", "access_request"], typing.Any
]
diff --git a/src/webflow/types/workspace_audit_log_item_payload_workspace_membership_method.py b/src/webflow/types/workspace_audit_log_item_payload_workspace_membership_method.py
index df8982f5..92ff0f2c 100644
--- a/src/webflow/types/workspace_audit_log_item_payload_workspace_membership_method.py
+++ b/src/webflow/types/workspace_audit_log_item_payload_workspace_membership_method.py
@@ -3,5 +3,5 @@
import typing
WorkspaceAuditLogItemPayloadWorkspaceMembershipMethod = typing.Union[
- typing.Literal["sso", "dashboard", "admin", "access_request"], typing.Any
+ typing.Literal["sso", "dashboard", "admin", "access_request", "api", "invite", "scim"], typing.Any
]
diff --git a/src/webflow/types/workspace_bulk_role_change_response.py b/src/webflow/types/workspace_bulk_role_change_response.py
new file mode 100644
index 00000000..85d3fb0a
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_bulk_role_change_response_results_item import WorkspaceBulkRoleChangeResponseResultsItem
+from .workspace_bulk_role_change_response_summary import WorkspaceBulkRoleChangeResponseSummary
+
+
+class WorkspaceBulkRoleChangeResponse(UniversalBaseModel):
+ """
+ Result of each update in the batch. A failed item does not fail the request.
+ """
+
+ summary: WorkspaceBulkRoleChangeResponseSummary = pydantic.Field()
+ """
+ Counts of updated and failed items
+ """
+
+ results: typing.List[WorkspaceBulkRoleChangeResponseResultsItem] = pydantic.Field()
+ """
+ Result for each id. Match results to the request by `id`, not by position.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_bulk_role_change_response_results_item.py b/src/webflow/types/workspace_bulk_role_change_response_results_item.py
new file mode 100644
index 00000000..425ce8ed
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_results_item.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_bulk_role_change_response_results_item_error import WorkspaceBulkRoleChangeResponseResultsItemError
+from .workspace_bulk_role_change_response_results_item_outcome import WorkspaceBulkRoleChangeResponseResultsItemOutcome
+from .workspace_bulk_role_change_response_results_item_type import WorkspaceBulkRoleChangeResponseResultsItemType
+
+
+class WorkspaceBulkRoleChangeResponseResultsItem(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ Unique identifier for the Member or invite
+ """
+
+ type: WorkspaceBulkRoleChangeResponseResultsItemType = pydantic.Field()
+ """
+ Whether `id` is a Member or an invite
+ """
+
+ outcome: WorkspaceBulkRoleChangeResponseResultsItemOutcome = pydantic.Field()
+ """
+ Whether the update was applied
+ """
+
+ error: typing.Optional[WorkspaceBulkRoleChangeResponseResultsItemError] = pydantic.Field(default=None)
+ """
+ Why the update failed. Present only when `outcome` is `failed`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_bulk_role_change_response_results_item_error.py b/src/webflow/types/workspace_bulk_role_change_response_results_item_error.py
new file mode 100644
index 00000000..cfe12981
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_results_item_error.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_bulk_role_change_response_results_item_error_code import (
+ WorkspaceBulkRoleChangeResponseResultsItemErrorCode,
+)
+
+
+class WorkspaceBulkRoleChangeResponseResultsItemError(UniversalBaseModel):
+ """
+ Why the update failed. Present only when `outcome` is `failed`.
+ """
+
+ code: typing.Optional[WorkspaceBulkRoleChangeResponseResultsItemErrorCode] = pydantic.Field(default=None)
+ """
+ Why this id failed. `workspace_member_not_found` and `workspace_invite_not_found` mean the id doesn't match a member or invite in this Workspace. `workspace_member_managed_by_scim` means the member is provisioned by your identity provider. `invalid_workspace_role` and `invalid_site_role` mean `role` or `defaultSiteRole` in the request isn't valid for this id, including a reviewer pairing mismatch. `cannot_update_user_role` covers every other role change this id can't accept. `internal_error` means the update failed for a reason unrelated to the request; retry it.
+ """
+
+ message: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Error message
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_bulk_role_change_response_results_item_error_code.py b/src/webflow/types/workspace_bulk_role_change_response_results_item_error_code.py
new file mode 100644
index 00000000..ff2a51de
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_results_item_error_code.py
@@ -0,0 +1,16 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceBulkRoleChangeResponseResultsItemErrorCode = typing.Union[
+ typing.Literal[
+ "workspace_member_not_found",
+ "workspace_invite_not_found",
+ "workspace_member_managed_by_scim",
+ "invalid_workspace_role",
+ "invalid_site_role",
+ "cannot_update_user_role",
+ "internal_error",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/workspace_bulk_role_change_response_results_item_outcome.py b/src/webflow/types/workspace_bulk_role_change_response_results_item_outcome.py
new file mode 100644
index 00000000..ef2111eb
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_results_item_outcome.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceBulkRoleChangeResponseResultsItemOutcome = typing.Union[typing.Literal["updated", "failed"], typing.Any]
diff --git a/src/webflow/types/workspace_bulk_role_change_response_results_item_type.py b/src/webflow/types/workspace_bulk_role_change_response_results_item_type.py
new file mode 100644
index 00000000..d71995b3
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_results_item_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceBulkRoleChangeResponseResultsItemType = typing.Union[typing.Literal["member", "invite"], typing.Any]
diff --git a/src/webflow/types/workspace_bulk_role_change_response_summary.py b/src/webflow/types/workspace_bulk_role_change_response_summary.py
new file mode 100644
index 00000000..1c0506c7
--- /dev/null
+++ b/src/webflow/types/workspace_bulk_role_change_response_summary.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+
+
+class WorkspaceBulkRoleChangeResponseSummary(UniversalBaseModel):
+ """
+ Counts of updated and failed items
+ """
+
+ updated: int = pydantic.Field()
+ """
+ Number of items updated
+ """
+
+ failed: int = pydantic.Field()
+ """
+ Number of items that failed
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_invitation_audit_log_item_event_sub_type.py b/src/webflow/types/workspace_invitation_audit_log_item_event_sub_type.py
index 07ccddb2..72686876 100644
--- a/src/webflow/types/workspace_invitation_audit_log_item_event_sub_type.py
+++ b/src/webflow/types/workspace_invitation_audit_log_item_event_sub_type.py
@@ -6,10 +6,11 @@
typing.Literal[
"invite_sent",
"invite_accepted",
- "invite_updated",
+ "invite_role_updated",
"invite_canceled",
"invite_declined",
"access_request_accepted",
+ "access_request_declined",
],
typing.Any,
]
diff --git a/src/webflow/types/workspace_invite.py b/src/webflow/types/workspace_invite.py
new file mode 100644
index 00000000..edb72dc6
--- /dev/null
+++ b/src/webflow/types/workspace_invite.py
@@ -0,0 +1,75 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .workspace_invite_default_site_role import WorkspaceInviteDefaultSiteRole
+from .workspace_invite_role import WorkspaceInviteRole
+from .workspace_invite_status import WorkspaceInviteStatus
+
+
+class WorkspaceInvite(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ Unique identifier for the invite
+ """
+
+ email: str = pydantic.Field()
+ """
+ Email address the invite was sent to
+ """
+
+ role: WorkspaceInviteRole = pydantic.Field()
+ """
+ The Workspace Role the invite grants on acceptance
+ """
+
+ created_on: typing_extensions.Annotated[
+ dt.datetime,
+ FieldMetadata(alias="createdOn"),
+ pydantic.Field(alias="createdOn", description="Date the invite was created"),
+ ]
+ status: WorkspaceInviteStatus = pydantic.Field()
+ """
+ Status of the invite. Only a `pending` invite can still be accepted.
+ """
+
+ sites: typing.List[str] = pydantic.Field()
+ """
+ IDs of the Sites the invite grants access to
+ """
+
+ all_sites: typing_extensions.Annotated[
+ bool,
+ FieldMetadata(alias="allSites"),
+ pydantic.Field(
+ alias="allSites",
+ description="Whether the invite grants access across the Workspace. Use this with `sites` to distinguish workspace-wide access from no Site access.",
+ ),
+ ]
+ default_site_role: typing_extensions.Annotated[
+ typing.Optional[WorkspaceInviteDefaultSiteRole],
+ FieldMetadata(alias="defaultSiteRole"),
+ pydantic.Field(
+ alias="defaultSiteRole",
+ description="The default Site Role the invite grants on acceptance. Legacy invites might not include this field.",
+ ),
+ ] = None
+ default_site_role_id: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="defaultSiteRoleId"),
+ pydantic.Field(alias="defaultSiteRoleId", description="ID of the custom Site Role named by `defaultSiteRole`"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_invite_default_site_role.py b/src/webflow/types/workspace_invite_default_site_role.py
new file mode 100644
index 00000000..0af0594e
--- /dev/null
+++ b/src/webflow/types/workspace_invite_default_site_role.py
@@ -0,0 +1,18 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceInviteDefaultSiteRole = typing.Union[
+ typing.Literal[
+ "site_manager",
+ "designer",
+ "branch_designer",
+ "limited_designer",
+ "marketer",
+ "content_editor",
+ "reviewer",
+ "viewer",
+ "custom",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/workspace_invite_role.py b/src/webflow/types/workspace_invite_role.py
new file mode 100644
index 00000000..badfa739
--- /dev/null
+++ b/src/webflow/types/workspace_invite_role.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceInviteRole = typing.Union[
+ typing.Literal["owner", "admin", "member", "guest", "reviewer", "client"], typing.Any
+]
diff --git a/src/webflow/types/workspace_invite_status.py b/src/webflow/types/workspace_invite_status.py
new file mode 100644
index 00000000..4c290124
--- /dev/null
+++ b/src/webflow/types/workspace_invite_status.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceInviteStatus = typing.Union[typing.Literal["pending", "declined", "expired"], typing.Any]
diff --git a/src/webflow/types/workspace_member.py b/src/webflow/types/workspace_member.py
new file mode 100644
index 00000000..cbbfad5d
--- /dev/null
+++ b/src/webflow/types/workspace_member.py
@@ -0,0 +1,55 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+from .workspace_member_roles import WorkspaceMemberRoles
+
+
+class WorkspaceMember(UniversalBaseModel):
+ id: str = pydantic.Field()
+ """
+ Unique identifier for the Member
+ """
+
+ email: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Email address of the Member
+ """
+
+ first_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="firstName"),
+ pydantic.Field(alias="firstName", description="First name of the Member"),
+ ] = None
+ last_name: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="lastName"),
+ pydantic.Field(alias="lastName", description="Last name of the Member"),
+ ] = None
+ roles: WorkspaceMemberRoles = pydantic.Field()
+ """
+ Roles the Member holds
+ """
+
+ added_on: typing_extensions.Annotated[
+ typing.Optional[dt.datetime],
+ FieldMetadata(alias="addedOn"),
+ pydantic.Field(
+ alias="addedOn",
+ description='When the Member most recently joined the Workspace. Not "member since": removing and re-adding a Member (including a support restore) reports the re-add, and a role change does not update it. For a Member who joined by invite, this is when they accepted, not when the invite was sent. `null` when unknown.',
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_member_roles.py b/src/webflow/types/workspace_member_roles.py
new file mode 100644
index 00000000..593db046
--- /dev/null
+++ b/src/webflow/types/workspace_member_roles.py
@@ -0,0 +1,33 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_member_roles_site import WorkspaceMemberRolesSite
+from .workspace_member_roles_workspace import WorkspaceMemberRolesWorkspace
+
+
+class WorkspaceMemberRoles(UniversalBaseModel):
+ """
+ Roles the Member holds
+ """
+
+ workspace: WorkspaceMemberRolesWorkspace = pydantic.Field()
+ """
+ The Member's role in the Workspace
+ """
+
+ site: typing.Optional[WorkspaceMemberRolesSite] = pydantic.Field(default=None)
+ """
+ The Member's default Site Role. List Workspace Members and Update Workspace Member Role return it only for Workspaces on Workspace seats.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_member_roles_site.py b/src/webflow/types/workspace_member_roles_site.py
new file mode 100644
index 00000000..3e567525
--- /dev/null
+++ b/src/webflow/types/workspace_member_roles_site.py
@@ -0,0 +1,18 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceMemberRolesSite = typing.Union[
+ typing.Literal[
+ "site_manager",
+ "designer",
+ "branch_designer",
+ "limited_designer",
+ "marketer",
+ "content_editor",
+ "reviewer",
+ "viewer",
+ "custom",
+ ],
+ typing.Any,
+]
diff --git a/src/webflow/types/workspace_member_roles_workspace.py b/src/webflow/types/workspace_member_roles_workspace.py
new file mode 100644
index 00000000..1631e38f
--- /dev/null
+++ b/src/webflow/types/workspace_member_roles_workspace.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+WorkspaceMemberRolesWorkspace = typing.Union[
+ typing.Literal["owner", "admin", "member", "guest", "reviewer", "client"], typing.Any
+]
diff --git a/src/webflow/types/workspace_members_response.py b/src/webflow/types/workspace_members_response.py
new file mode 100644
index 00000000..c3dc33dd
--- /dev/null
+++ b/src/webflow/types/workspace_members_response.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace_invite import WorkspaceInvite
+from .workspace_member import WorkspaceMember
+from .workspace_members_response_pagination import WorkspaceMembersResponsePagination
+
+
+class WorkspaceMembersResponse(UniversalBaseModel):
+ members: typing.List[WorkspaceMember] = pydantic.Field()
+ """
+ List of Workspace Members
+ """
+
+ invites: typing.List[WorkspaceInvite] = pydantic.Field()
+ """
+ List of pending, declined and expired invites
+ """
+
+ pagination: WorkspaceMembersResponsePagination = pydantic.Field()
+ """
+ Pagination object
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspace_members_response_pagination.py b/src/webflow/types/workspace_members_response_pagination.py
new file mode 100644
index 00000000..0893a53d
--- /dev/null
+++ b/src/webflow/types/workspace_members_response_pagination.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from ..core.serialization import FieldMetadata
+
+
+class WorkspaceMembersResponsePagination(UniversalBaseModel):
+ """
+ Pagination object
+ """
+
+ limit: int = pydantic.Field()
+ """
+ The limit used for pagination, applied to `members` and `invites` separately
+ """
+
+ next_cursor: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="nextCursor"),
+ pydantic.Field(
+ alias="nextCursor",
+ description="Cursor for the next page of both lists. `null` when there are no more results.",
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/webflow/types/workspaces.py b/src/webflow/types/workspaces.py
new file mode 100644
index 00000000..bac3387e
--- /dev/null
+++ b/src/webflow/types/workspaces.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
+from .workspace import Workspace
+
+
+class Workspaces(UniversalBaseModel):
+ workspaces: typing.List[Workspace]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
From 9a9d440a9b29d18cb7786f6aef01408898435b40 Mon Sep 17 00:00:00 2001
From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com>
Date: Thu, 15 Oct 2026 18:34:35 +0000
Subject: [PATCH 2/2] [fern-replay] advance lockfile
---
.fern/replay.lock | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.fern/replay.lock b/.fern/replay.lock
index ef809498..81906e1e 100644
--- a/.fern/replay.lock
+++ b/.fern/replay.lock
@@ -6,5 +6,11 @@ generations:
timestamp: 2026-10-15T18:34:30.958Z
cli_version: unknown
generator_versions: {}
-current_generation: 1e6e7e02a6c6be33f48b6569f6ca20dc332a6c1c
+ - commit_sha: c6796dc125178af1ed2c5ca738ec02f97d4ae318
+ tree_hash: 979fd73cc2a705d80236ed0c44a9fea0c6dedc2a
+ timestamp: 2026-10-15T18:34:31.678Z
+ cli_version: unknown
+ generator_versions:
+ fernapi/fern-python-sdk: 4.63.4
+current_generation: c6796dc125178af1ed2c5ca738ec02f97d4ae318
patches: []