wxcli 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wxcli/__init__.py +4 -0
- wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
- wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
- wxcli/_playbook/.claude/rules/cleanup.md +29 -0
- wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
- wxcli/_playbook/.claude/rules/org-health.md +14 -0
- wxcli/_playbook/.claude/settings.json +8 -0
- wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
- wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
- wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
- wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
- wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
- wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
- wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
- wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
- wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
- wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
- wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
- wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
- wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
- wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
- wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
- wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
- wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
- wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
- wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
- wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
- wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
- wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
- wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
- wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
- wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
- wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
- wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
- wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
- wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
- wxcli/_playbook/CLAUDE.md +373 -0
- wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
- wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
- wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
- wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
- wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
- wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
- wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
- wxcli/_playbook/docs/reference/admin-partner.md +357 -0
- wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
- wxcli/_playbook/docs/reference/authentication.md +965 -0
- wxcli/_playbook/docs/reference/call-control.md +1205 -0
- wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
- wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
- wxcli/_playbook/docs/reference/call-routing.md +2304 -0
- wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
- wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
- wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
- wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
- wxcli/_playbook/docs/reference/devices-core.md +1869 -0
- wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
- wxcli/_playbook/docs/reference/devices-platform.md +467 -0
- wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
- wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
- wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
- wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
- wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
- wxcli/_playbook/docs/reference/meetings-content.md +473 -0
- wxcli/_playbook/docs/reference/meetings-core.md +903 -0
- wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
- wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
- wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
- wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
- wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
- wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
- wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
- wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
- wxcli/_playbook/docs/reference/provisioning.md +1366 -0
- wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
- wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
- wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
- wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
- wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
- wxcli/_version.py +24 -0
- wxcli/auth.py +137 -0
- wxcli/commands/__init__.py +0 -0
- wxcli/commands/_registry.py +181 -0
- wxcli/commands/activation_email.py +103 -0
- wxcli/commands/admin_recordings.py +585 -0
- wxcli/commands/analytics.py +105 -0
- wxcli/commands/announcement_playlists.py +219 -0
- wxcli/commands/announcements.py +519 -0
- wxcli/commands/archive_users.py +36 -0
- wxcli/commands/attachment_actions.py +60 -0
- wxcli/commands/audit_events.py +83 -0
- wxcli/commands/authorizations.py +113 -0
- wxcli/commands/auto_attendant.py +642 -0
- wxcli/commands/broadworks_billing_reports.py +126 -0
- wxcli/commands/broadworks_enterprises.py +160 -0
- wxcli/commands/broadworks_subscribers.py +319 -0
- wxcli/commands/broadworks_workspaces.py +108 -0
- wxcli/commands/call_controls.py +1304 -0
- wxcli/commands/call_park.py +494 -0
- wxcli/commands/call_pickup.py +229 -0
- wxcli/commands/call_queue.py +1878 -0
- wxcli/commands/call_recording.py +707 -0
- wxcli/commands/call_routing.py +1610 -0
- wxcli/commands/call_settings_for_me_phase_5.py +212 -0
- wxcli/commands/caller_reputation.py +169 -0
- wxcli/commands/calling_service.py +305 -0
- wxcli/commands/cc_address_book.py +770 -0
- wxcli/commands/cc_agent_greetings.py +242 -0
- wxcli/commands/cc_agent_summaries.py +39 -0
- wxcli/commands/cc_agent_wellbeing.py +216 -0
- wxcli/commands/cc_agents.py +425 -0
- wxcli/commands/cc_ai_assistant.py +55 -0
- wxcli/commands/cc_ai_feature.py +294 -0
- wxcli/commands/cc_audio_files.py +149 -0
- wxcli/commands/cc_auto_csat.py +311 -0
- wxcli/commands/cc_aux_code.py +458 -0
- wxcli/commands/cc_business_hour.py +353 -0
- wxcli/commands/cc_call_monitoring.py +228 -0
- wxcli/commands/cc_callbacks.py +194 -0
- wxcli/commands/cc_campaign.py +212 -0
- wxcli/commands/cc_captures.py +39 -0
- wxcli/commands/cc_contact_list.py +169 -0
- wxcli/commands/cc_contact_number.py +295 -0
- wxcli/commands/cc_data_sources.py +235 -0
- wxcli/commands/cc_desktop_layout.py +380 -0
- wxcli/commands/cc_desktop_profile.py +322 -0
- wxcli/commands/cc_dial_number.py +511 -0
- wxcli/commands/cc_dial_plan.py +347 -0
- wxcli/commands/cc_dnc.py +101 -0
- wxcli/commands/cc_entry_point.py +545 -0
- wxcli/commands/cc_ewt.py +51 -0
- wxcli/commands/cc_flow.py +477 -0
- wxcli/commands/cc_global_vars.py +430 -0
- wxcli/commands/cc_holiday_list.py +335 -0
- wxcli/commands/cc_journey.py +1261 -0
- wxcli/commands/cc_legacy_flows.py +49 -0
- wxcli/commands/cc_multimedia_profile.py +436 -0
- wxcli/commands/cc_notification.py +51 -0
- wxcli/commands/cc_outdial_ani.py +604 -0
- wxcli/commands/cc_overrides.py +341 -0
- wxcli/commands/cc_queue.py +1083 -0
- wxcli/commands/cc_queue_stats.py +55 -0
- wxcli/commands/cc_realtime.py +51 -0
- wxcli/commands/cc_resource_collection.py +312 -0
- wxcli/commands/cc_search.py +46 -0
- wxcli/commands/cc_site.py +400 -0
- wxcli/commands/cc_skill.py +350 -0
- wxcli/commands/cc_skill_profile.py +289 -0
- wxcli/commands/cc_subscriptions.py +426 -0
- wxcli/commands/cc_summaries.py +130 -0
- wxcli/commands/cc_tasks.py +844 -0
- wxcli/commands/cc_team.py +325 -0
- wxcli/commands/cc_user_profiles.py +349 -0
- wxcli/commands/cc_users.py +701 -0
- wxcli/commands/cc_work_types.py +363 -0
- wxcli/commands/cdr.py +89 -0
- wxcli/commands/classifications.py +38 -0
- wxcli/commands/cleanup.py +1427 -0
- wxcli/commands/client_settings.py +72 -0
- wxcli/commands/conference.py +328 -0
- wxcli/commands/configure.py +72 -0
- wxcli/commands/converged_recordings.py +347 -0
- wxcli/commands/converged_recordings_export.py +310 -0
- wxcli/commands/cq_playlists.py +42 -0
- wxcli/commands/cucm.py +2794 -0
- wxcli/commands/cucm_config.py +129 -0
- wxcli/commands/customer_assist.py +373 -0
- wxcli/commands/data_sources.py +227 -0
- wxcli/commands/dect_devices.py +787 -0
- wxcli/commands/device_configurations.py +77 -0
- wxcli/commands/device_dynamic_settings.py +320 -0
- wxcli/commands/device_settings.py +1489 -0
- wxcli/commands/devices.py +280 -0
- wxcli/commands/domains.py +142 -0
- wxcli/commands/ecm.py +178 -0
- wxcli/commands/emergency_services.py +827 -0
- wxcli/commands/events.py +83 -0
- wxcli/commands/external_voicemail.py +52 -0
- wxcli/commands/groups.py +206 -0
- wxcli/commands/guest_management.py +76 -0
- wxcli/commands/hds.py +340 -0
- wxcli/commands/hot_desk.py +67 -0
- wxcli/commands/hot_desking_members.py +118 -0
- wxcli/commands/hot_desking_portal.py +124 -0
- wxcli/commands/hunt_group.py +577 -0
- wxcli/commands/hybrid_clusters.py +71 -0
- wxcli/commands/hybrid_connectors.py +67 -0
- wxcli/commands/identity_org.py +159 -0
- wxcli/commands/init_playbook.py +155 -0
- wxcli/commands/licenses.py +106 -0
- wxcli/commands/live_monitoring.py +40 -0
- wxcli/commands/location_call_handling.py +577 -0
- wxcli/commands/location_schedules.py +341 -0
- wxcli/commands/location_settings.py +1297 -0
- wxcli/commands/location_voicemail.py +494 -0
- wxcli/commands/locations.py +336 -0
- wxcli/commands/meeting_captions.py +110 -0
- wxcli/commands/meeting_chats.py +63 -0
- wxcli/commands/meeting_invitees.py +234 -0
- wxcli/commands/meeting_messages.py +28 -0
- wxcli/commands/meeting_participants.py +278 -0
- wxcli/commands/meeting_polls.py +111 -0
- wxcli/commands/meeting_preferences.py +515 -0
- wxcli/commands/meeting_qa.py +78 -0
- wxcli/commands/meeting_qualities.py +43 -0
- wxcli/commands/meeting_reports.py +101 -0
- wxcli/commands/meeting_session_types.py +105 -0
- wxcli/commands/meeting_site.py +57 -0
- wxcli/commands/meeting_slido.py +44 -0
- wxcli/commands/meeting_summaries.py +92 -0
- wxcli/commands/meeting_tracking_codes.py +233 -0
- wxcli/commands/meeting_transcripts.py +230 -0
- wxcli/commands/meetings.py +1997 -0
- wxcli/commands/memberships.py +164 -0
- wxcli/commands/messages.py +213 -0
- wxcli/commands/mode_management.py +244 -0
- wxcli/commands/my_call_settings.py +3577 -0
- wxcli/commands/numbers.py +405 -0
- wxcli/commands/operating_modes.py +410 -0
- wxcli/commands/org_contacts.py +284 -0
- wxcli/commands/org_health_cli.py +55 -0
- wxcli/commands/org_settings.py +71 -0
- wxcli/commands/organizations.py +83 -0
- wxcli/commands/paging_group.py +257 -0
- wxcli/commands/partner_admins.py +141 -0
- wxcli/commands/partner_reports.py +188 -0
- wxcli/commands/partner_tags.py +216 -0
- wxcli/commands/people.py +279 -0
- wxcli/commands/person_call_settings.py +71 -0
- wxcli/commands/pstn.py +280 -0
- wxcli/commands/recording_report.py +154 -0
- wxcli/commands/report_templates.py +38 -0
- wxcli/commands/reports.py +144 -0
- wxcli/commands/resource_group_memberships.py +160 -0
- wxcli/commands/resource_groups.py +67 -0
- wxcli/commands/roles.py +63 -0
- wxcli/commands/room_tabs.py +160 -0
- wxcli/commands/rooms.py +225 -0
- wxcli/commands/scim_bulk.py +46 -0
- wxcli/commands/scim_groups.py +246 -0
- wxcli/commands/scim_schemas.py +82 -0
- wxcli/commands/scim_users.py +283 -0
- wxcli/commands/security_audit.py +54 -0
- wxcli/commands/service_apps.py +51 -0
- wxcli/commands/single_number_reach.py +209 -0
- wxcli/commands/team_memberships.py +155 -0
- wxcli/commands/teams.py +153 -0
- wxcli/commands/ucm_profile.py +42 -0
- wxcli/commands/update.py +219 -0
- wxcli/commands/user_settings.py +5116 -0
- wxcli/commands/video_mesh.py +1090 -0
- wxcli/commands/virtual_extensions.py +502 -0
- wxcli/commands/virtual_line_settings.py +2072 -0
- wxcli/commands/webhooks.py +176 -0
- wxcli/commands/wholesale_billing_reports.py +147 -0
- wxcli/commands/wholesale_provisioning.py +523 -0
- wxcli/commands/workspace_locations.py +339 -0
- wxcli/commands/workspace_metrics.py +103 -0
- wxcli/commands/workspace_personalization.py +70 -0
- wxcli/commands/workspace_settings.py +3347 -0
- wxcli/commands/workspaces.py +279 -0
- wxcli/commands/xapi.py +114 -0
- wxcli/config.py +139 -0
- wxcli/errors.py +77 -0
- wxcli/main.py +210 -0
- wxcli/migration/CLAUDE.md +106 -0
- wxcli/migration/__init__.py +1 -0
- wxcli/migration/advisory/CLAUDE.md +196 -0
- wxcli/migration/advisory/__init__.py +46 -0
- wxcli/migration/advisory/advisor.py +107 -0
- wxcli/migration/advisory/advisory_patterns.py +2552 -0
- wxcli/migration/advisory/recommendation_rules.py +835 -0
- wxcli/migration/cucm/CLAUDE.md +74 -0
- wxcli/migration/cucm/__init__.py +5 -0
- wxcli/migration/cucm/connection.py +265 -0
- wxcli/migration/cucm/discovery.py +225 -0
- wxcli/migration/cucm/extractors/__init__.py +1 -0
- wxcli/migration/cucm/extractors/announcements.py +92 -0
- wxcli/migration/cucm/extractors/base.py +90 -0
- wxcli/migration/cucm/extractors/device_profiles.py +98 -0
- wxcli/migration/cucm/extractors/devices.py +193 -0
- wxcli/migration/cucm/extractors/e911.py +103 -0
- wxcli/migration/cucm/extractors/features.py +309 -0
- wxcli/migration/cucm/extractors/helpers.py +73 -0
- wxcli/migration/cucm/extractors/informational.py +295 -0
- wxcli/migration/cucm/extractors/locations.py +221 -0
- wxcli/migration/cucm/extractors/moh.py +100 -0
- wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
- wxcli/migration/cucm/extractors/routing.py +493 -0
- wxcli/migration/cucm/extractors/shared_lines.py +146 -0
- wxcli/migration/cucm/extractors/templates.py +229 -0
- wxcli/migration/cucm/extractors/tier4.py +173 -0
- wxcli/migration/cucm/extractors/users.py +240 -0
- wxcli/migration/cucm/extractors/voicemail.py +243 -0
- wxcli/migration/cucm/extractors/workspaces.py +88 -0
- wxcli/migration/cucm/unity_connection.py +304 -0
- wxcli/migration/execute/CLAUDE.md +372 -0
- wxcli/migration/execute/__init__.py +303 -0
- wxcli/migration/execute/batch.py +277 -0
- wxcli/migration/execute/dependency.py +483 -0
- wxcli/migration/execute/engine.py +964 -0
- wxcli/migration/execute/handlers.py +2232 -0
- wxcli/migration/execute/planner.py +1939 -0
- wxcli/migration/execute/runtime.py +571 -0
- wxcli/migration/export/CLAUDE.md +49 -0
- wxcli/migration/export/__init__.py +8 -0
- wxcli/migration/export/csv_export.py +126 -0
- wxcli/migration/export/deployment_plan.py +479 -0
- wxcli/migration/export/json_export.py +60 -0
- wxcli/migration/models.py +941 -0
- wxcli/migration/phone_models.py +50 -0
- wxcli/migration/preflight/CLAUDE.md +52 -0
- wxcli/migration/preflight/__init__.py +89 -0
- wxcli/migration/preflight/checks.py +911 -0
- wxcli/migration/preflight/runner.py +293 -0
- wxcli/migration/rate_limiter.py +144 -0
- wxcli/migration/report/CLAUDE.md +245 -0
- wxcli/migration/report/__init__.py +7 -0
- wxcli/migration/report/appendix.py +2437 -0
- wxcli/migration/report/assembler.py +183 -0
- wxcli/migration/report/charts.py +263 -0
- wxcli/migration/report/executive.py +776 -0
- wxcli/migration/report/explainer.py +704 -0
- wxcli/migration/report/helpers.py +74 -0
- wxcli/migration/report/ingest.py +152 -0
- wxcli/migration/report/notice_templates.py +192 -0
- wxcli/migration/report/score.py +375 -0
- wxcli/migration/report/styles.py +1052 -0
- wxcli/migration/report/user_diff.py +1052 -0
- wxcli/migration/report/user_notice.py +425 -0
- wxcli/migration/state.py +139 -0
- wxcli/migration/store.py +860 -0
- wxcli/migration/transform/CLAUDE.md +107 -0
- wxcli/migration/transform/__init__.py +12 -0
- wxcli/migration/transform/analysis_pipeline.py +453 -0
- wxcli/migration/transform/analyzers/__init__.py +142 -0
- wxcli/migration/transform/analyzers/css_permission.py +189 -0
- wxcli/migration/transform/analyzers/css_routing.py +335 -0
- wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
- wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
- wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
- wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
- wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
- wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
- wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
- wxcli/migration/transform/analyzers/missing_data.py +300 -0
- wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
- wxcli/migration/transform/analyzers/shared_line.py +170 -0
- wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
- wxcli/migration/transform/analyzers/workspace_license.py +212 -0
- wxcli/migration/transform/cross_reference.py +1136 -0
- wxcli/migration/transform/cucm_pattern.py +246 -0
- wxcli/migration/transform/decisions.py +301 -0
- wxcli/migration/transform/e164.py +145 -0
- wxcli/migration/transform/engine.py +256 -0
- wxcli/migration/transform/mappers/CLAUDE.md +123 -0
- wxcli/migration/transform/mappers/__init__.py +76 -0
- wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
- wxcli/migration/transform/mappers/base.py +190 -0
- wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
- wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
- wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
- wxcli/migration/transform/mappers/css_mapper.py +868 -0
- wxcli/migration/transform/mappers/dect_mapper.py +367 -0
- wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
- wxcli/migration/transform/mappers/device_mapper.py +227 -0
- wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
- wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
- wxcli/migration/transform/mappers/e911_mapper.py +129 -0
- wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
- wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
- wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
- wxcli/migration/transform/mappers/line_mapper.py +310 -0
- wxcli/migration/transform/mappers/location_mapper.py +235 -0
- wxcli/migration/transform/mappers/moh_mapper.py +114 -0
- wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
- wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
- wxcli/migration/transform/mappers/routing_mapper.py +821 -0
- wxcli/migration/transform/mappers/snr_mapper.py +201 -0
- wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
- wxcli/migration/transform/mappers/user_mapper.py +264 -0
- wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
- wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
- wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
- wxcli/migration/transform/normalizers.py +2008 -0
- wxcli/migration/transform/pattern_converter.py +106 -0
- wxcli/migration/transform/pipeline.py +271 -0
- wxcli/migration/transform/rules.py +255 -0
- wxcli/org_health/CLAUDE.md +63 -0
- wxcli/org_health/__init__.py +7 -0
- wxcli/org_health/analyze.py +93 -0
- wxcli/org_health/checks.py +458 -0
- wxcli/org_health/collector.py +62 -0
- wxcli/org_health/models.py +78 -0
- wxcli/org_health/report.py +300 -0
- wxcli/output.py +101 -0
- wxcli-1.2.0.dist-info/METADATA +505 -0
- wxcli-1.2.0.dist-info/RECORD +413 -0
- wxcli-1.2.0.dist-info/WHEEL +5 -0
- wxcli-1.2.0.dist-info/entry_points.txt +2 -0
- wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
- wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
- wxcli-1.2.0.dist-info/scm_version.json +8 -0
- wxcli-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
# Location Call Settings — Announcements, Playlists, Schedules & Access Codes
|
|
2
|
+
|
|
3
|
+
## Sources
|
|
4
|
+
|
|
5
|
+
- wxc_sdk v1.30.0
|
|
6
|
+
- OpenAPI spec: specs/webex-cloud-calling.json
|
|
7
|
+
- developer.webex.com Location Call Settings APIs
|
|
8
|
+
|
|
9
|
+
Reference for managing audio media (announcements, playlists), time-based routing (schedules), and outgoing-permission bypass codes at the location and org level via the `wxc_sdk`.
|
|
10
|
+
|
|
11
|
+
> **Not supported for Webex for Government (FedRAMP)** — Announcements and Playlists APIs are explicitly excluded. See [authentication.md → FedRAMP](authentication.md#webex-for-government-fedramp) for all FedRAMP restrictions.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Announcements Repository
|
|
16
|
+
|
|
17
|
+
The announcement repository stores binary audio files (WAV) used by Auto Attendants, Call Queues, and Music On Hold. Files can be uploaded at the **organization level** or scoped to a **specific location**.
|
|
18
|
+
|
|
19
|
+
**SDK access path:** `api.telephony.announcements_repo`
|
|
20
|
+
|
|
21
|
+
**API class:** `AnnouncementsRepositoryApi` (base: `telephony/config`)
|
|
22
|
+
|
|
23
|
+
### 1.1 Data Models
|
|
24
|
+
|
|
25
|
+
#### `RepoAnnouncement` (extends `IdAndName`)
|
|
26
|
+
|
|
27
|
+
| Field | Type | Notes |
|
|
28
|
+
|-------|------|-------|
|
|
29
|
+
| `id` | `str` | Unique identifier |
|
|
30
|
+
| `name` | `str` | Display name |
|
|
31
|
+
| `file_name` | `str` | Uploaded binary file name |
|
|
32
|
+
| `file_size` | `int` | Size in kilobytes |
|
|
33
|
+
| `media_file_type` | `MediaFileType` | Audio/video media type |
|
|
34
|
+
| `last_updated` | `datetime` | UTC timestamp |
|
|
35
|
+
| `level` | `AnnouncementLevel` | Org-level vs. location-level |
|
|
36
|
+
| `location` | `IdAndName` | Location details (if location-scoped) |
|
|
37
|
+
| `feature_reference_count` | `int` | Number of features referencing this file (**details only**, not returned by list) |
|
|
38
|
+
| `feature_references` | `list[FeatureReference]` | Features using this announcement (**details only**) |
|
|
39
|
+
| `playlists` | `list[IdAndName]` | Playlists containing this announcement (**details only**) |
|
|
40
|
+
|
|
41
|
+
#### `FeatureReference`
|
|
42
|
+
|
|
43
|
+
Describes a call feature (Auto Attendant, Call Queue, Music On Hold) that references an announcement.
|
|
44
|
+
|
|
45
|
+
| Field | Type | Notes |
|
|
46
|
+
|-------|------|-------|
|
|
47
|
+
| `id` | `str` | Feature identifier |
|
|
48
|
+
| `name` | `str` | Feature name |
|
|
49
|
+
| `type` | `str` | Resource type of the feature |
|
|
50
|
+
| `location_id` | `str` | Location identifier |
|
|
51
|
+
| `location_name` | `str` | Location name |
|
|
52
|
+
|
|
53
|
+
#### `RepositoryUsage`
|
|
54
|
+
|
|
55
|
+
| Field | Type | Notes |
|
|
56
|
+
|-------|------|-------|
|
|
57
|
+
| `total_file_size_used_kb` | `int` | Total space used (KB) |
|
|
58
|
+
| `max_audio_file_size_allowed_kb` | `int` | Max single audio file size (KB) |
|
|
59
|
+
| `max_video_file_size_allowed_kb` | `int` | Max single video file size (KB) |
|
|
60
|
+
| `total_file_size_limit_mb` | `int` | Total repo capacity (MB) |
|
|
61
|
+
|
|
62
|
+
### 1.2 Methods
|
|
63
|
+
|
|
64
|
+
#### `list(...)` — List announcement greetings
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
def list(
|
|
68
|
+
self,
|
|
69
|
+
location_id: str = None, # None = org-level; "all" or "locations" or specific ID
|
|
70
|
+
order: str = None, # Sort by "fileName" or "fileSize" (ascending default)
|
|
71
|
+
file_name: str = None, # Filter by file name
|
|
72
|
+
file_type: str = None, # Filter by file type
|
|
73
|
+
media_file_type: str = None, # Filter by media file type
|
|
74
|
+
name: str = None, # Filter by announcement label
|
|
75
|
+
org_id: str = None,
|
|
76
|
+
**params
|
|
77
|
+
) -> Generator[RepoAnnouncement, None, None]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Returns a **paginated generator** of `RepoAnnouncement` objects.
|
|
81
|
+
- Without `location_id`, returns enterprise (org) level announcements.
|
|
82
|
+
- With `location_id` set to a specific location ID, returns that location's announcements.
|
|
83
|
+
- Special values for `location_id`: `"all"` (all levels), `"locations"` (all location-level).
|
|
84
|
+
|
|
85
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
86
|
+
|
|
87
|
+
#### `upload_announcement(...)` — Upload a new audio file
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
def upload_announcement(
|
|
91
|
+
self,
|
|
92
|
+
name: str, # Display name (required)
|
|
93
|
+
file: Union[BufferedReader, str], # File path (str) or open binary reader
|
|
94
|
+
upload_as: str = None, # Filename for upload (required if file is a reader)
|
|
95
|
+
location_id: str = None, # None = org level
|
|
96
|
+
org_id: str = None,
|
|
97
|
+
) -> str # Returns the new announcement ID
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- Accepts a **file path** (str) or a **BufferedReader** / `BytesIO` object.
|
|
101
|
+
- When passing a reader, `upload_as` is required and must be a `.wav` filename.
|
|
102
|
+
- Uses `multipart/form-data` with `audio/wav` content type internally.
|
|
103
|
+
- Without `location_id`, uploads to org-level repository.
|
|
104
|
+
- With `location_id`, uploads to that location's repository.
|
|
105
|
+
|
|
106
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
107
|
+
|
|
108
|
+
**Usage examples:**
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Upload from file path
|
|
112
|
+
ann_id = api.telephony.announcements_repo.upload_announcement(
|
|
113
|
+
name='Welcome Greeting', file='/path/to/greeting.wav'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Upload from open file handle
|
|
117
|
+
with open('greeting.wav', mode='rb') as f:
|
|
118
|
+
ann_id = api.telephony.announcements_repo.upload_announcement(
|
|
119
|
+
name='Welcome Greeting', file=f, upload_as='greeting.wav'
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Upload from bytes via BytesIO
|
|
123
|
+
import io
|
|
124
|
+
binary_file = io.BytesIO(wav_bytes)
|
|
125
|
+
ann_id = api.telephony.announcements_repo.upload_announcement(
|
|
126
|
+
name='Welcome Greeting', file=binary_file, upload_as='greeting.wav'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Upload to a specific location
|
|
130
|
+
ann_id = api.telephony.announcements_repo.upload_announcement(
|
|
131
|
+
name='Location Greeting', file='greeting.wav', location_id='<location_id>'
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### `details(...)` — Get single announcement details
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
def details(
|
|
139
|
+
self,
|
|
140
|
+
announcement_id: str,
|
|
141
|
+
location_id: str = None, # None = org level
|
|
142
|
+
org_id: str = None,
|
|
143
|
+
) -> RepoAnnouncement
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- Returns `feature_reference_count`, `feature_references`, and `playlists` (which `list()` does not).
|
|
147
|
+
|
|
148
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
149
|
+
|
|
150
|
+
#### `modify(...)` — Replace an existing announcement file
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
def modify(
|
|
154
|
+
self,
|
|
155
|
+
announcement_id: str,
|
|
156
|
+
name: str, # New display name
|
|
157
|
+
file: Union[BufferedReader, str], # Replacement file
|
|
158
|
+
upload_as: str = None,
|
|
159
|
+
location_id: str = None,
|
|
160
|
+
org_id: str = None,
|
|
161
|
+
) -> str # Returns announcement ID
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- Same file-handling semantics as `upload_announcement`.
|
|
165
|
+
- Uses `multipart/form-data` PUT request internally.
|
|
166
|
+
|
|
167
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
168
|
+
|
|
169
|
+
#### `delete(...)` — Delete an announcement
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
def delete(
|
|
173
|
+
self,
|
|
174
|
+
announcement_id: str,
|
|
175
|
+
location_id: str = None, # None = org level
|
|
176
|
+
org_id: str = None,
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
181
|
+
|
|
182
|
+
#### `usage(...)` — Get repository storage usage
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
def usage(
|
|
186
|
+
self,
|
|
187
|
+
location_id: str = None, # None = org level
|
|
188
|
+
org_id: str = None,
|
|
189
|
+
) -> RepositoryUsage
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- Returns file size limits and current usage.
|
|
193
|
+
|
|
194
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
195
|
+
|
|
196
|
+
### 1.3 Raw HTTP — Announcements
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
BASE = "https://webexapis.com/v1"
|
|
200
|
+
|
|
201
|
+
# --- Org-level announcements ---
|
|
202
|
+
|
|
203
|
+
# GET — List org-level announcements (paginated)
|
|
204
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements",
|
|
205
|
+
params={"max": 1000})
|
|
206
|
+
# Response: {"announcements": [{"id": "...", "name": "...", "fileName": "...", "fileSize": 123, ...}]}
|
|
207
|
+
|
|
208
|
+
# GET — Get org-level announcement details
|
|
209
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements/{ann_id}")
|
|
210
|
+
# Response includes featureReferenceCount, featureReferences, playlists (not returned by list)
|
|
211
|
+
|
|
212
|
+
# PUT — Update org-level announcement (metadata only; file upload uses multipart)
|
|
213
|
+
body = {"name": "Updated Name"}
|
|
214
|
+
api.session.rest_put(f"{BASE}/telephony/config/announcements/{ann_id}", json=body)
|
|
215
|
+
|
|
216
|
+
# DELETE — Delete org-level announcement
|
|
217
|
+
api.session.rest_delete(f"{BASE}/telephony/config/announcements/{ann_id}")
|
|
218
|
+
|
|
219
|
+
# GET — Org-level repository usage
|
|
220
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements/usage")
|
|
221
|
+
# Response: {"totalFileSizeUsedKB": 1024, "maxAudioFileSizeAllowedKB": 8192,
|
|
222
|
+
# "maxVideoFileSizeAllowedKB": 16384, "totalFileSizeLimitMB": 500}
|
|
223
|
+
|
|
224
|
+
# --- Location-level announcements ---
|
|
225
|
+
|
|
226
|
+
# GET — List location announcements (use locationId query param on org endpoint)
|
|
227
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements",
|
|
228
|
+
params={"locationId": loc_id, "max": 1000})
|
|
229
|
+
|
|
230
|
+
# GET — Get location announcement details
|
|
231
|
+
result = api.session.rest_get(
|
|
232
|
+
f"{BASE}/telephony/config/locations/{loc_id}/announcements/{ann_id}")
|
|
233
|
+
|
|
234
|
+
# PUT — Update location announcement
|
|
235
|
+
api.session.rest_put(
|
|
236
|
+
f"{BASE}/telephony/config/locations/{loc_id}/announcements/{ann_id}", json=body)
|
|
237
|
+
|
|
238
|
+
# DELETE — Delete location announcement
|
|
239
|
+
api.session.rest_delete(
|
|
240
|
+
f"{BASE}/telephony/config/locations/{loc_id}/announcements/{ann_id}")
|
|
241
|
+
|
|
242
|
+
# GET — Location repository usage
|
|
243
|
+
result = api.session.rest_get(
|
|
244
|
+
f"{BASE}/telephony/config/locations/{loc_id}/announcements/usage")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Gotcha:** The list endpoint uses a query parameter `locationId` on the org-level URL (`/telephony/config/announcements`), but details/update/delete for location announcements use a path segment (`/locations/{locId}/announcements/{annId}`). These are different URL patterns for list vs. CRUD.
|
|
248
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
249
|
+
|
|
250
|
+
**Gotcha:** Upload and modify (file replacement) use `multipart/form-data` with `audio/wav` content type. The SDK handles this via `upload_announcement()` and `modify()`. For raw HTTP, you would need to construct the multipart body manually -- the `rest_put`/`rest_post` JSON helpers do not handle file uploads.
|
|
251
|
+
|
|
252
|
+
### CLI Examples
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# List all org-level announcements
|
|
256
|
+
wxcli announcements list
|
|
257
|
+
|
|
258
|
+
# List announcements for a specific location
|
|
259
|
+
wxcli announcements list --location-id Y2lzY29zcGFyazovL_LOC_ID
|
|
260
|
+
|
|
261
|
+
# List all announcements across all locations
|
|
262
|
+
wxcli announcements list --location-id all
|
|
263
|
+
|
|
264
|
+
# Filter by file name or announcement name
|
|
265
|
+
wxcli announcements list --file-name "greeting.wav"
|
|
266
|
+
wxcli announcements list --name "Welcome Greeting"
|
|
267
|
+
|
|
268
|
+
# Get org-level repository usage (total space, limits)
|
|
269
|
+
wxcli announcements show
|
|
270
|
+
|
|
271
|
+
# Get details for an org-level announcement
|
|
272
|
+
wxcli announcements show-announcements-config Y2lzY29zcGFyazovL_ANN_ID
|
|
273
|
+
|
|
274
|
+
# Get details for a location-level announcement
|
|
275
|
+
wxcli announcements show-announcements-locations Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_ANN_ID
|
|
276
|
+
|
|
277
|
+
# Get repository usage for a specific location
|
|
278
|
+
wxcli announcements show-usage Y2lzY29zcGFyazovL_LOC_ID
|
|
279
|
+
|
|
280
|
+
# Delete an org-level announcement
|
|
281
|
+
wxcli announcements delete Y2lzY29zcGFyazovL_ANN_ID --force
|
|
282
|
+
|
|
283
|
+
# Delete a location-level announcement
|
|
284
|
+
wxcli announcements delete-announcements Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_ANN_ID --force
|
|
285
|
+
|
|
286
|
+
# Update an org-level announcement (metadata via --json-body)
|
|
287
|
+
wxcli announcements update Y2lzY29zcGFyazovL_ANN_ID --json-body '{"name": "Updated Greeting"}'
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
> **Note:** File upload (creating new announcements) requires `multipart/form-data` which the CLI does not support. Use the SDK `upload_announcement()` method or raw HTTP with `curl` for uploads.
|
|
291
|
+
|
|
292
|
+
### 1.4 Key Patterns
|
|
293
|
+
|
|
294
|
+
- **Org vs. location scoping:** Every method takes an optional `location_id`. When `None`, the operation targets the org-level repository. When set, it targets that location's repository.
|
|
295
|
+
- **URL pattern:** Org-level uses `/telephony/config/announcements/...`; location-level uses `/telephony/config/locations/{locationId}/announcements/...`.
|
|
296
|
+
- **File format:** WAV audio files. The SDK hard-codes `audio/wav` as the MIME type for uploads.
|
|
297
|
+
- **Checking references before delete:** Use `details()` to inspect `feature_references` and `playlists` before deleting an announcement that may be in use.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 2. Playlists (Music On Hold)
|
|
302
|
+
|
|
303
|
+
Playlists group multiple announcement files for Music On Hold. A playlist can contain up to **25 announcement files** and can be assigned to one or more locations.
|
|
304
|
+
|
|
305
|
+
**SDK access path:** `api.telephony.playlists`
|
|
306
|
+
|
|
307
|
+
**API class:** `PlayListApi` (base: `telephony/config/announcements/playlists`)
|
|
308
|
+
|
|
309
|
+
### 2.1 Data Models
|
|
310
|
+
|
|
311
|
+
#### `PlayList`
|
|
312
|
+
|
|
313
|
+
| Field | Type | Notes |
|
|
314
|
+
|-------|------|-------|
|
|
315
|
+
| `id` | `str` | Unique identifier |
|
|
316
|
+
| `name` | `str` | Playlist display name |
|
|
317
|
+
| `file_size` | `int` | Total size of files (KB) |
|
|
318
|
+
| `file_count` | `int` | Number of announcements in playlist |
|
|
319
|
+
| `is_in_use` | `bool` | Whether any feature references this playlist |
|
|
320
|
+
| `last_updated` | `datetime` | UTC timestamp |
|
|
321
|
+
| `level` | `str` | Level at which playlist exists |
|
|
322
|
+
| `location_count` | `int` | Number of locations assigned to this playlist |
|
|
323
|
+
| `announcements` | `list[PlaylistAnnouncement]` | Announcement details (populated in `details()`) |
|
|
324
|
+
|
|
325
|
+
#### `PlaylistAnnouncement`
|
|
326
|
+
|
|
327
|
+
| Field | Type | Notes |
|
|
328
|
+
|-------|------|-------|
|
|
329
|
+
| `id` | `str` | Announcement identifier |
|
|
330
|
+
| `name` | `str` | Announcement name |
|
|
331
|
+
| `file_name` | `str` | Uploaded file name |
|
|
332
|
+
| `file_size` | `int` | Size in KB |
|
|
333
|
+
| `media_file_type` | `str` | Media type |
|
|
334
|
+
| `last_updated` | `datetime` | UTC timestamp |
|
|
335
|
+
| `level` | `str` | Announcement level |
|
|
336
|
+
|
|
337
|
+
#### `PlaylistUsage`
|
|
338
|
+
|
|
339
|
+
| Field | Type | Notes |
|
|
340
|
+
|-------|------|-------|
|
|
341
|
+
| `id` | `str` | Playlist identifier |
|
|
342
|
+
| `locations` | `list[PlaylistUsageLocation]` | Locations using this playlist |
|
|
343
|
+
|
|
344
|
+
#### `PlaylistUsageLocation`
|
|
345
|
+
|
|
346
|
+
| Field | Type | Notes |
|
|
347
|
+
|-------|------|-------|
|
|
348
|
+
| `id` | `str` | Location identifier |
|
|
349
|
+
| `name` | `str` | Location name |
|
|
350
|
+
| `feature_reference` | `PlaylistUsageLocationFeatureRef` | Feature referencing the playlist |
|
|
351
|
+
|
|
352
|
+
#### `PlaylistUsageType` (enum)
|
|
353
|
+
|
|
354
|
+
| Value | Description |
|
|
355
|
+
|-------|-------------|
|
|
356
|
+
| `feature` | Filter usage by feature |
|
|
357
|
+
| `location` | Filter usage by location |
|
|
358
|
+
|
|
359
|
+
### 2.2 Methods
|
|
360
|
+
|
|
361
|
+
#### `list(...)` — List all playlists
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
def list(
|
|
365
|
+
self,
|
|
366
|
+
org_id: str = None,
|
|
367
|
+
) -> list[PlayList]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
- Returns a **list** (not a generator) of all playlists in the org.
|
|
371
|
+
- Does **not** use pagination.
|
|
372
|
+
|
|
373
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
374
|
+
|
|
375
|
+
#### `create(...)` — Create a new playlist
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
def create(
|
|
379
|
+
self,
|
|
380
|
+
name: str, # Unique playlist name
|
|
381
|
+
announcement_ids: list[str], # IDs of announcements to include (max 25)
|
|
382
|
+
org_id: str = None,
|
|
383
|
+
) -> str # Returns the new playlist ID
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
- **Max 25 announcements** per playlist.
|
|
387
|
+
- Announcements must already exist in the repository.
|
|
388
|
+
|
|
389
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
390
|
+
|
|
391
|
+
#### `details(...)` — Get playlist details
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
def details(
|
|
395
|
+
self,
|
|
396
|
+
play_list_id: str,
|
|
397
|
+
org_id: str = None,
|
|
398
|
+
) -> PlayList
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
- Returns full details including the `announcements` list.
|
|
402
|
+
|
|
403
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
404
|
+
|
|
405
|
+
#### `modify(...)` — Update a playlist
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
def modify(
|
|
409
|
+
self,
|
|
410
|
+
play_list_id: str,
|
|
411
|
+
name: str = None, # New name (optional)
|
|
412
|
+
announcement_ids: list[str] = None, # New set of announcement IDs (optional)
|
|
413
|
+
org_id: str = None,
|
|
414
|
+
)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
- You can update the name, the announcement list, or both.
|
|
418
|
+
|
|
419
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
420
|
+
|
|
421
|
+
#### `delete(...)` — Delete a playlist
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
def delete(
|
|
425
|
+
self,
|
|
426
|
+
play_list_id: str,
|
|
427
|
+
org_id: str = None,
|
|
428
|
+
)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
432
|
+
|
|
433
|
+
#### `usage(...)` — Get playlist usage
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
def usage(
|
|
437
|
+
self,
|
|
438
|
+
play_list_id: str,
|
|
439
|
+
playlist_usage_type: PlaylistUsageType = None, # Filter by 'feature' or 'location'
|
|
440
|
+
) -> PlaylistUsage
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- Returns which locations and features reference this playlist.
|
|
444
|
+
|
|
445
|
+
- **Scopes:** `spark-admin:telephony_config_read` (the `PlayListApi` class docstring confirms all read operations use `spark-admin:telephony_config_read`; the `usage()` method itself has no explicit scope docstring but inherits from the class-level scope)
|
|
446
|
+
|
|
447
|
+
#### `assigned_locations(...)` — List locations assigned to a playlist
|
|
448
|
+
|
|
449
|
+
```python
|
|
450
|
+
def assigned_locations(
|
|
451
|
+
self,
|
|
452
|
+
play_list_id: str,
|
|
453
|
+
org_id: str = None,
|
|
454
|
+
) -> list[IdAndName]
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
- Returns `IdAndName` objects (id + name) for each assigned location.
|
|
458
|
+
|
|
459
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
460
|
+
|
|
461
|
+
#### `modify_assigned_locations(...)` — Assign playlist to locations
|
|
462
|
+
|
|
463
|
+
```python
|
|
464
|
+
def modify_assigned_locations(
|
|
465
|
+
self,
|
|
466
|
+
play_list_id: str,
|
|
467
|
+
location_ids: list[str], # Full list of location IDs to assign
|
|
468
|
+
org_id: str = None,
|
|
469
|
+
)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- This **replaces** the entire location assignment list. To add a location, you must include all existing location IDs plus the new one.
|
|
473
|
+
- Assigning a playlist to a location sets it as that location's Music On Hold source.
|
|
474
|
+
|
|
475
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
476
|
+
|
|
477
|
+
### 2.3 Raw HTTP — Playlists
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
BASE = "https://webexapis.com/v1"
|
|
481
|
+
|
|
482
|
+
# GET — List all playlists (org-level, not paginated)
|
|
483
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements/playlists")
|
|
484
|
+
# Response: {"playlists": [{"id": "...", "name": "...", "fileSize": 0, "fileCount": 0,
|
|
485
|
+
# "isInUse": false, "lastUpdated": "...", "locationCount": 0}]}
|
|
486
|
+
|
|
487
|
+
# POST — Create a playlist
|
|
488
|
+
body = {
|
|
489
|
+
"name": "Hold Music Rotation",
|
|
490
|
+
"announcements": [{"id": "<ann_id_1>"}, {"id": "<ann_id_2>"}]
|
|
491
|
+
}
|
|
492
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/announcements/playlists", json=body)
|
|
493
|
+
# Returns: {"id": "<playlist_id>"}
|
|
494
|
+
|
|
495
|
+
# GET — Get playlist details (includes announcement list)
|
|
496
|
+
result = api.session.rest_get(f"{BASE}/telephony/config/announcements/playlists/{playlist_id}")
|
|
497
|
+
|
|
498
|
+
# PUT — Update a playlist (name and/or announcements)
|
|
499
|
+
body = {
|
|
500
|
+
"name": "Updated Playlist Name",
|
|
501
|
+
"announcements": [{"id": "<ann_id_1>"}, {"id": "<ann_id_3>"}]
|
|
502
|
+
}
|
|
503
|
+
api.session.rest_put(f"{BASE}/telephony/config/announcements/playlists/{playlist_id}", json=body)
|
|
504
|
+
|
|
505
|
+
# DELETE — Delete a playlist
|
|
506
|
+
api.session.rest_delete(f"{BASE}/telephony/config/announcements/playlists/{playlist_id}")
|
|
507
|
+
|
|
508
|
+
# --- Playlist location assignments ---
|
|
509
|
+
|
|
510
|
+
# GET — List locations assigned to a playlist
|
|
511
|
+
result = api.session.rest_get(
|
|
512
|
+
f"{BASE}/telephony/config/announcements/playlists/{playlist_id}/locations")
|
|
513
|
+
# Response: {"locations": [{"id": "...", "name": "..."}]}
|
|
514
|
+
|
|
515
|
+
# PUT — Assign playlist to locations (full replace)
|
|
516
|
+
body = {
|
|
517
|
+
"locationIds": ["<loc_id_1>", "<loc_id_2>"]
|
|
518
|
+
}
|
|
519
|
+
api.session.rest_put(
|
|
520
|
+
f"{BASE}/telephony/config/announcements/playlists/{playlist_id}/locations", json=body)
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Gotcha:** Location assignment is a full replace, not incremental. Always GET current locations first, then PUT the complete list including any new additions.
|
|
524
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
525
|
+
|
|
526
|
+
### CLI Examples
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# List all playlists in the org
|
|
530
|
+
wxcli announcement-playlists list
|
|
531
|
+
|
|
532
|
+
# Get details for a specific playlist (includes announcement list)
|
|
533
|
+
wxcli announcement-playlists show Y2lzY29zcGFyazovL_PLAYLIST_ID
|
|
534
|
+
|
|
535
|
+
# Create a playlist (announcement IDs must be passed via --json-body)
|
|
536
|
+
wxcli announcement-playlists create --name "Hold Music Rotation" \
|
|
537
|
+
--json-body '{"name": "Hold Music Rotation", "announcementIds": ["ANN_ID_1", "ANN_ID_2"]}'
|
|
538
|
+
|
|
539
|
+
# Update a playlist name
|
|
540
|
+
wxcli announcement-playlists update Y2lzY29zcGFyazovL_PLAYLIST_ID --name "New Playlist Name"
|
|
541
|
+
|
|
542
|
+
# Update a playlist's announcements via --json-body
|
|
543
|
+
wxcli announcement-playlists update Y2lzY29zcGFyazovL_PLAYLIST_ID \
|
|
544
|
+
--json-body '{"name": "Hold Music Rotation", "announcementIds": ["ANN_ID_1", "ANN_ID_3"]}'
|
|
545
|
+
|
|
546
|
+
# Delete a playlist
|
|
547
|
+
wxcli announcement-playlists delete Y2lzY29zcGFyazovL_PLAYLIST_ID --force
|
|
548
|
+
|
|
549
|
+
# List locations assigned to a playlist
|
|
550
|
+
wxcli announcement-playlists list-playlists Y2lzY29zcGFyazovL_PLAYLIST_ID
|
|
551
|
+
|
|
552
|
+
# Assign a playlist to locations (full replace — include ALL desired location IDs)
|
|
553
|
+
wxcli announcement-playlists update-playlists Y2lzY29zcGFyazovL_PLAYLIST_ID \
|
|
554
|
+
--json-body '{"locationIds": ["LOC_ID_1", "LOC_ID_2"]}'
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### CLI: `cq-playlists` (Call Queue Playlist Usage)
|
|
558
|
+
|
|
559
|
+
The `cq-playlists` CLI group lets you check which call queues or locations reference a specific playlist.
|
|
560
|
+
|
|
561
|
+
| Command | Description |
|
|
562
|
+
|---------|-------------|
|
|
563
|
+
| `cq-playlists list <playlist_id>` | Get playlist usage (which queues/locations reference a playlist) |
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
# See which features use a specific playlist
|
|
567
|
+
wxcli cq-playlists list <playlist_id>
|
|
568
|
+
|
|
569
|
+
# Filter to see only location-level usage
|
|
570
|
+
wxcli cq-playlists list <playlist_id> --playlist-usage-type location
|
|
571
|
+
|
|
572
|
+
# Filter to see only feature-level usage (call queues, auto attendants)
|
|
573
|
+
wxcli cq-playlists list <playlist_id> --playlist-usage-type feature
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### 2.4 Key Patterns
|
|
577
|
+
|
|
578
|
+
- **Playlists are org-level only.** They are created at the org level, then assigned to locations via `modify_assigned_locations()`.
|
|
579
|
+
- **Workflow:** Upload announcements to the repo first, then create a playlist referencing those announcement IDs, then assign the playlist to locations.
|
|
580
|
+
- **Location assignment is a full replace**, not incremental. Always read current assignments with `assigned_locations()` before modifying.
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## 3. Access Codes
|
|
585
|
+
|
|
586
|
+
Access codes (also called authorization codes) let authorized users bypass outgoing or incoming calling permission restrictions. They exist at two levels: **location** and **organization**.
|
|
587
|
+
|
|
588
|
+
### 3.1 Location Access Codes
|
|
589
|
+
|
|
590
|
+
**SDK access path:** `api.telephony.access_codes`
|
|
591
|
+
|
|
592
|
+
**API class:** `LocationAccessCodesApi` (base: `telephony/config/locations`)
|
|
593
|
+
|
|
594
|
+
**Endpoint pattern:** `/telephony/config/locations/{locationId}/outgoingPermission/accessCodes`
|
|
595
|
+
|
|
596
|
+
#### Data Model: `AuthCode`
|
|
597
|
+
|
|
598
|
+
| Field | Type | Notes |
|
|
599
|
+
|-------|------|-------|
|
|
600
|
+
| `code` | `str` | The authorization code string |
|
|
601
|
+
| `description` | `str` | Human-readable description |
|
|
602
|
+
| `level` | `AuthCodeLevel` | `LOCATION` or `CUSTOM` (read-only, set by system) |
|
|
603
|
+
|
|
604
|
+
#### Methods
|
|
605
|
+
|
|
606
|
+
##### `read(...)` — List access codes for a location
|
|
607
|
+
|
|
608
|
+
```python
|
|
609
|
+
def read(
|
|
610
|
+
self,
|
|
611
|
+
location_id: str,
|
|
612
|
+
org_id: str = None,
|
|
613
|
+
) -> list[AuthCode]
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
617
|
+
|
|
618
|
+
##### `create(...)` — Add access codes to a location
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
def create(
|
|
622
|
+
self,
|
|
623
|
+
location_id: str,
|
|
624
|
+
access_codes: list[AuthCode], # One or more codes to add
|
|
625
|
+
org_id: str = None,
|
|
626
|
+
) -> list[AuthCode]
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
- Can add multiple codes in a single call.
|
|
630
|
+
|
|
631
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
632
|
+
|
|
633
|
+
##### `delete_codes(...)` — Delete specific access codes
|
|
634
|
+
|
|
635
|
+
```python
|
|
636
|
+
def delete_codes(
|
|
637
|
+
self,
|
|
638
|
+
location_id: str,
|
|
639
|
+
access_codes: list[Union[str, AuthCode]], # Code strings or AuthCode objects
|
|
640
|
+
org_id: str = None,
|
|
641
|
+
) -> list[AuthCode]
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
- Accepts either `AuthCode` objects or plain code strings.
|
|
645
|
+
- Uses a PUT request with `deleteCodes` in the body (not a DELETE endpoint).
|
|
646
|
+
|
|
647
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
648
|
+
|
|
649
|
+
##### `delete_all(...)` — Delete all access codes for a location
|
|
650
|
+
|
|
651
|
+
```python
|
|
652
|
+
def delete_all(
|
|
653
|
+
self,
|
|
654
|
+
location_id: str,
|
|
655
|
+
org_id: str = None,
|
|
656
|
+
)
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
- Wipes all codes for the specified location.
|
|
660
|
+
|
|
661
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
662
|
+
|
|
663
|
+
### 3.2 Organization Access Codes
|
|
664
|
+
|
|
665
|
+
**SDK access path:** `api.telephony.org_access_codes`
|
|
666
|
+
|
|
667
|
+
**API class:** `OrganisationAccessCodesApi` (base: `telephony/config/outgoingPermission/accessCodes`)
|
|
668
|
+
|
|
669
|
+
Organization-level access codes apply across all locations in the org.
|
|
670
|
+
|
|
671
|
+
#### Methods
|
|
672
|
+
|
|
673
|
+
##### `list(...)` — List org-level access codes
|
|
674
|
+
|
|
675
|
+
```python
|
|
676
|
+
def list(
|
|
677
|
+
self,
|
|
678
|
+
code: list[str] = None, # Filter by code values (comma-separated internally)
|
|
679
|
+
description: list[str] = None, # Filter by descriptions (comma-separated internally)
|
|
680
|
+
org_id: str = None,
|
|
681
|
+
**params
|
|
682
|
+
) -> Generator[AuthCode, None, None]
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
- Returns a **paginated generator** (unlike the location-level `read()` which returns a plain list).
|
|
686
|
+
- Supports filtering by code and description.
|
|
687
|
+
|
|
688
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
689
|
+
|
|
690
|
+
##### `create(...)` — Create org-level access codes
|
|
691
|
+
|
|
692
|
+
```python
|
|
693
|
+
def create(
|
|
694
|
+
self,
|
|
695
|
+
access_codes: list[AuthCode], # Max 10,000 per request
|
|
696
|
+
org_id: str = None,
|
|
697
|
+
)
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
- Supports up to **10,000 codes per request**.
|
|
701
|
+
|
|
702
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
703
|
+
|
|
704
|
+
##### `delete(...)` — Delete org-level access codes
|
|
705
|
+
|
|
706
|
+
```python
|
|
707
|
+
def delete(
|
|
708
|
+
self,
|
|
709
|
+
delete_codes: list[str] = None, # Code strings to delete (max 10,000 per request)
|
|
710
|
+
org_id: str = None,
|
|
711
|
+
)
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
- Supports up to **10,000 codes per request**.
|
|
715
|
+
- Uses a PUT request with `deleteCodes` in the body (same pattern as location-level).
|
|
716
|
+
|
|
717
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
718
|
+
|
|
719
|
+
### 3.3 Raw HTTP — Access Codes
|
|
720
|
+
|
|
721
|
+
```python
|
|
722
|
+
BASE = "https://webexapis.com/v1"
|
|
723
|
+
|
|
724
|
+
# --- Location-level access codes ---
|
|
725
|
+
|
|
726
|
+
# GET — List access codes for a location
|
|
727
|
+
result = api.session.rest_get(
|
|
728
|
+
f"{BASE}/telephony/config/locations/{loc_id}/outgoingPermission/accessCodes")
|
|
729
|
+
# Response: {"accessCodes": [{"code": "1234", "description": "Sales team", "level": "LOCATION"}]}
|
|
730
|
+
|
|
731
|
+
# POST — Create access codes for a location
|
|
732
|
+
body = {
|
|
733
|
+
"accessCodes": [
|
|
734
|
+
{"code": "1234", "description": "Sales team"},
|
|
735
|
+
{"code": "5678", "description": "Support team"}
|
|
736
|
+
]
|
|
737
|
+
}
|
|
738
|
+
api.session.rest_post(
|
|
739
|
+
f"{BASE}/telephony/config/locations/{loc_id}/outgoingPermission/accessCodes", json=body)
|
|
740
|
+
|
|
741
|
+
# PUT — Delete specific access codes (uses PUT, not DELETE)
|
|
742
|
+
body = {
|
|
743
|
+
"deleteCodes": ["1234", "5678"]
|
|
744
|
+
}
|
|
745
|
+
api.session.rest_put(
|
|
746
|
+
f"{BASE}/telephony/config/locations/{loc_id}/outgoingPermission/accessCodes", json=body)
|
|
747
|
+
|
|
748
|
+
# DELETE — Delete all access codes for a location
|
|
749
|
+
api.session.rest_delete(
|
|
750
|
+
f"{BASE}/telephony/config/locations/{loc_id}/outgoingPermission/accessCodes")
|
|
751
|
+
|
|
752
|
+
# --- Org-level access codes ---
|
|
753
|
+
|
|
754
|
+
# GET — List org-level access codes (paginated)
|
|
755
|
+
result = api.session.rest_get(
|
|
756
|
+
f"{BASE}/telephony/config/outgoingPermission/accessCodes",
|
|
757
|
+
params={"max": 1000})
|
|
758
|
+
# Response: {"accessCodes": [{"code": "9999", "description": "Global bypass"}]}
|
|
759
|
+
|
|
760
|
+
# POST — Create org-level access codes (max 10,000 per request)
|
|
761
|
+
body = {
|
|
762
|
+
"accessCodes": [
|
|
763
|
+
{"code": "9999", "description": "Global bypass"}
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
api.session.rest_post(f"{BASE}/telephony/config/outgoingPermission/accessCodes", json=body)
|
|
767
|
+
|
|
768
|
+
# PUT — Delete org-level access codes (uses PUT, not DELETE; max 10,000 per request)
|
|
769
|
+
body = {
|
|
770
|
+
"deleteCodes": ["9999"]
|
|
771
|
+
}
|
|
772
|
+
api.session.rest_put(f"{BASE}/telephony/config/outgoingPermission/accessCodes", json=body)
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Gotcha:** Deleting specific codes uses PUT with a `deleteCodes` array in the body, not an HTTP DELETE. This applies at both location and org level. The `DELETE` verb is only used for "delete all" at the location level.
|
|
776
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
777
|
+
|
|
778
|
+
### 3.4 Key Patterns
|
|
779
|
+
|
|
780
|
+
- **Location vs. org level:** Location codes bypass permissions for persons/workspaces at that location only. Org codes apply across all locations.
|
|
781
|
+
- **No update method:** To change an access code, delete the old one and create a new one.
|
|
782
|
+
- **Delete uses PUT:** Both location and org `delete` methods send a PUT with `deleteCodes` in the body, not an HTTP DELETE. The SDK handles this transparently.
|
|
783
|
+
- **Batch limits:** Org-level create and delete support up to 10,000 codes per request (confirmed in SDK docstrings: "max limit is 10k per request"). Location-level create/delete has no documented batch limit in the SDK source -- the `LocationAccessCodesApi.create()` and `delete_codes()` methods accept a `list[AuthCode]` with no stated maximum.
|
|
784
|
+
|
|
785
|
+
---
|
|
786
|
+
|
|
787
|
+
## 4. Schedules & Holiday Schedules
|
|
788
|
+
|
|
789
|
+
Schedules define time windows (business hours, holidays) that control call routing behavior for features like Auto Attendants. They can be created at the **location** or **person (user)** level.
|
|
790
|
+
|
|
791
|
+
**SDK access path:** `api.telephony.schedules`
|
|
792
|
+
|
|
793
|
+
**API class:** `ScheduleApi` (base depends on context: `telephony/config/locations` for location schedules, `people` for user schedules)
|
|
794
|
+
|
|
795
|
+
### 4.1 Data Models
|
|
796
|
+
|
|
797
|
+
#### `ScheduleType` (enum)
|
|
798
|
+
|
|
799
|
+
| Value | String | Description |
|
|
800
|
+
|-------|--------|-------------|
|
|
801
|
+
| `business_hours` | `"businessHours"` | Define operating hours |
|
|
802
|
+
| `holidays` | `"holidays"` | Define exceptions to business hours |
|
|
803
|
+
|
|
804
|
+
#### `ScheduleLevel` (enum)
|
|
805
|
+
|
|
806
|
+
| Value | Description |
|
|
807
|
+
|-------|-------------|
|
|
808
|
+
| `LOCATION` | Location-scoped schedule |
|
|
809
|
+
| `ORGANIZATION` | Org-scoped schedule |
|
|
810
|
+
| `PEOPLE` | User-scoped schedule |
|
|
811
|
+
|
|
812
|
+
#### `Schedule`
|
|
813
|
+
|
|
814
|
+
| Field | Type | Notes |
|
|
815
|
+
|-------|------|-------|
|
|
816
|
+
| `name` | `str` | Schedule display name |
|
|
817
|
+
| `new_name` | `str` | New name (only used in `update()`) |
|
|
818
|
+
| `schedule_id` | `str` | Unique identifier (alias: `id`) |
|
|
819
|
+
| `level` | `ScheduleLevel` | Scope level (returned in user-level listing) |
|
|
820
|
+
| `location_name` | `str` | Location name (returned by `list()` for location schedules) |
|
|
821
|
+
| `location_id` | `str` | Location identifier (returned by `list()` for location schedules) |
|
|
822
|
+
| `schedule_type` | `ScheduleType` | `businessHours` or `holidays` (alias: `type`) |
|
|
823
|
+
| `events` | `list[Event]` | List of events in this schedule |
|
|
824
|
+
|
|
825
|
+
**Convenience constructor:**
|
|
826
|
+
|
|
827
|
+
```python
|
|
828
|
+
Schedule.business(
|
|
829
|
+
name: str,
|
|
830
|
+
day_start: Union[int, datetime.time] = 9,
|
|
831
|
+
day_end: Union[int, datetime.time] = 17,
|
|
832
|
+
break_start: Union[int, datetime.time] = 12,
|
|
833
|
+
break_end: Union[int, datetime.time] = 13,
|
|
834
|
+
) -> Schedule
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
Creates a Mon-Fri business hours schedule with two events per day (morning + afternoon, split by lunch break). Integers are interpreted as hours.
|
|
838
|
+
|
|
839
|
+
#### `Event`
|
|
840
|
+
|
|
841
|
+
| Field | Type | Notes |
|
|
842
|
+
|-------|------|-------|
|
|
843
|
+
| `event_id` | `str` | Unique identifier (alias: `id`) |
|
|
844
|
+
| `name` | `str` | Event name |
|
|
845
|
+
| `new_name` | `str` | New name (only used in updates) |
|
|
846
|
+
| `start_date` | `date` | Start date (required if `all_day_enabled` is set) |
|
|
847
|
+
| `end_date` | `date` | End date (required if `all_day_enabled` is set) |
|
|
848
|
+
| `start_time` | `time` | Start time (required if `all_day_enabled` is false/omitted) |
|
|
849
|
+
| `end_time` | `time` | End time (required if `all_day_enabled` is false/omitted) |
|
|
850
|
+
| `all_day_enabled` | `bool` | True for all-day events (e.g., holidays) |
|
|
851
|
+
| `recurrence` | `Recurrence` | Optional recurrence pattern |
|
|
852
|
+
|
|
853
|
+
**Convenience constructor:**
|
|
854
|
+
|
|
855
|
+
```python
|
|
856
|
+
Event.day_start_end(
|
|
857
|
+
name: str,
|
|
858
|
+
day: datetime.date,
|
|
859
|
+
start_time: Union[int, datetime.time],
|
|
860
|
+
end_time: Union[int, datetime.time],
|
|
861
|
+
) -> Event
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
Creates an event on a specific day with given start/end times and a weekly recurrence on that day of the week.
|
|
865
|
+
|
|
866
|
+
#### `Recurrence`
|
|
867
|
+
|
|
868
|
+
Controls how events repeat. Location and user schedules support different recurrence types:
|
|
869
|
+
|
|
870
|
+
| Field | Type | Supported Level |
|
|
871
|
+
|-------|------|-----------------|
|
|
872
|
+
| `recur_for_ever` | `bool` | User + Location |
|
|
873
|
+
| `recur_end_date` | `date` | User + Location |
|
|
874
|
+
| `recur_end_occurrence` | `int` | User only |
|
|
875
|
+
| `recur_daily` | `RecurDaily` | User only |
|
|
876
|
+
| `recur_weekly` | `RecurWeekly` | User + Location |
|
|
877
|
+
| `recur_yearly_by_date` | `RecurYearlyByDate` | Location only |
|
|
878
|
+
| `recur_yearly_by_day` | `RecurYearlyByDay` | Location only |
|
|
879
|
+
|
|
880
|
+
**Convenience constructor:**
|
|
881
|
+
|
|
882
|
+
```python
|
|
883
|
+
Recurrence.every_week(day: Union[ScheduleDay, datetime.date]) -> Recurrence
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Creates a weekly recurrence for a single day that repeats forever.
|
|
887
|
+
|
|
888
|
+
#### Supporting Recurrence Models
|
|
889
|
+
|
|
890
|
+
- **`RecurWeekly`** — boolean flags for each day of the week + `recur_interval` (weeks between occurrences). Helper: `RecurWeekly.single_day(day, recur_interval=1)`.
|
|
891
|
+
- **`RecurYearlyByDate`** — `day_of_month` (int) + `month` (ScheduleMonth enum). Helper: `RecurYearlyByDate.from_date(date)`.
|
|
892
|
+
- **`RecurYearlyByDay`** — `day` (ScheduleDay) + `week` (ScheduleWeek: FIRST/SECOND/THIRD/FOURTH) + `month` (ScheduleMonth).
|
|
893
|
+
- **`RecurDaily`** — `recur_interval` (int, days between occurrences). User schedules only.
|
|
894
|
+
|
|
895
|
+
### 4.2 Methods
|
|
896
|
+
|
|
897
|
+
All schedule methods take `obj_id` as the first parameter, which is a **location ID** (for location schedules) or a **user/person ID** (for user schedules).
|
|
898
|
+
|
|
899
|
+
#### `list(...)` — List schedules
|
|
900
|
+
|
|
901
|
+
```python
|
|
902
|
+
def list(
|
|
903
|
+
self,
|
|
904
|
+
obj_id: str, # Location or user ID
|
|
905
|
+
org_id: str = None,
|
|
906
|
+
schedule_type: ScheduleType = None, # Filter by businessHours or holidays
|
|
907
|
+
name: str = None, # Filter by name
|
|
908
|
+
**params
|
|
909
|
+
) -> Generator[Schedule, None, None]
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
- Returns a **paginated generator**.
|
|
913
|
+
- Listing does **not** include events; use `details()` to get events.
|
|
914
|
+
|
|
915
|
+
**Required scope:** Depends on schedule level. The wxc_sdk `ScheduleApi.list()` docstring says `spark-admin:people_read` (this applies when listing **person/user** schedules via `/people/{id}/features/schedules`). For **location** schedules via `/telephony/config/locations/{id}/schedules`, the OpenAPI spec requires `spark-admin:telephony_config_read`.
|
|
916
|
+
|
|
917
|
+
#### `details(...)` — Get schedule details with events
|
|
918
|
+
|
|
919
|
+
```python
|
|
920
|
+
def details(
|
|
921
|
+
self,
|
|
922
|
+
obj_id: str,
|
|
923
|
+
schedule_type: ScheduleTypeOrStr,
|
|
924
|
+
schedule_id: str,
|
|
925
|
+
org_id: str = None,
|
|
926
|
+
) -> Schedule
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
- Returns the full schedule including all `events`.
|
|
930
|
+
|
|
931
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
932
|
+
|
|
933
|
+
#### `create(...)` — Create a schedule
|
|
934
|
+
|
|
935
|
+
```python
|
|
936
|
+
def create(
|
|
937
|
+
self,
|
|
938
|
+
obj_id: str,
|
|
939
|
+
schedule: Schedule, # Must include name, schedule_type, and events
|
|
940
|
+
org_id: str = None,
|
|
941
|
+
) -> str # Returns the new schedule ID
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
945
|
+
|
|
946
|
+
#### `update(...)` — Update a schedule
|
|
947
|
+
|
|
948
|
+
```python
|
|
949
|
+
def update(
|
|
950
|
+
self,
|
|
951
|
+
obj_id: str,
|
|
952
|
+
schedule: Schedule,
|
|
953
|
+
schedule_type: ScheduleTypeOrStr = None, # Default: from schedule object
|
|
954
|
+
schedule_id: str = None, # Default: from schedule object
|
|
955
|
+
org_id: str = None,
|
|
956
|
+
) -> str # Returns schedule ID (changes if name changed)
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
> **Important:** The schedule ID **changes** if the schedule name is modified.
|
|
960
|
+
|
|
961
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
962
|
+
|
|
963
|
+
#### `delete_schedule(...)` — Delete a schedule
|
|
964
|
+
|
|
965
|
+
```python
|
|
966
|
+
def delete_schedule(
|
|
967
|
+
self,
|
|
968
|
+
obj_id: str,
|
|
969
|
+
schedule_type: ScheduleTypeOrStr,
|
|
970
|
+
schedule_id: str,
|
|
971
|
+
org_id: str = None,
|
|
972
|
+
)
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
976
|
+
|
|
977
|
+
#### `event_details(...)` — Get a single event
|
|
978
|
+
|
|
979
|
+
```python
|
|
980
|
+
def event_details(
|
|
981
|
+
self,
|
|
982
|
+
obj_id: str,
|
|
983
|
+
schedule_type: ScheduleTypeOrStr,
|
|
984
|
+
schedule_id: str,
|
|
985
|
+
event_id: str,
|
|
986
|
+
org_id: str = None,
|
|
987
|
+
) -> Event
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Required scope:** `spark-admin:telephony_config_read`
|
|
991
|
+
|
|
992
|
+
#### `event_create(...)` — Add an event to an existing schedule
|
|
993
|
+
|
|
994
|
+
```python
|
|
995
|
+
def event_create(
|
|
996
|
+
self,
|
|
997
|
+
obj_id: str,
|
|
998
|
+
schedule_type: ScheduleTypeOrStr,
|
|
999
|
+
schedule_id: str,
|
|
1000
|
+
event: Event,
|
|
1001
|
+
org_id: str = None,
|
|
1002
|
+
) -> str # Returns the new event ID
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
1006
|
+
|
|
1007
|
+
#### `event_update(...)` — Update an event
|
|
1008
|
+
|
|
1009
|
+
```python
|
|
1010
|
+
def event_update(
|
|
1011
|
+
self,
|
|
1012
|
+
obj_id: str,
|
|
1013
|
+
schedule_type: ScheduleTypeOrStr,
|
|
1014
|
+
schedule_id: str,
|
|
1015
|
+
event: Event,
|
|
1016
|
+
event_id: str = None, # Default: from event object
|
|
1017
|
+
org_id: str = None,
|
|
1018
|
+
) -> str # Returns event ID (changes if name changed)
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
> **Important:** The event ID **changes** if the event name is modified.
|
|
1022
|
+
|
|
1023
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
1024
|
+
|
|
1025
|
+
#### `event_delete(...)` — Delete an event
|
|
1026
|
+
|
|
1027
|
+
```python
|
|
1028
|
+
def event_delete(
|
|
1029
|
+
self,
|
|
1030
|
+
obj_id: str,
|
|
1031
|
+
schedule_type: ScheduleTypeOrStr,
|
|
1032
|
+
schedule_id: str,
|
|
1033
|
+
event_id: str,
|
|
1034
|
+
org_id: str = None,
|
|
1035
|
+
)
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
**Required scope:** `spark-admin:telephony_config_write`
|
|
1039
|
+
|
|
1040
|
+
### 4.3 Raw HTTP — Schedules
|
|
1041
|
+
|
|
1042
|
+
```python
|
|
1043
|
+
BASE = "https://webexapis.com/v1"
|
|
1044
|
+
|
|
1045
|
+
# --- Location schedules ---
|
|
1046
|
+
|
|
1047
|
+
# GET — List schedules for a location (paginated)
|
|
1048
|
+
result = api.session.rest_get(
|
|
1049
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules",
|
|
1050
|
+
params={"max": 1000, "type": "businessHours"} # or "holidays"
|
|
1051
|
+
)
|
|
1052
|
+
# Response: {"schedules": [{"id": "...", "name": "...", "type": "businessHours"}]}
|
|
1053
|
+
# Note: list does NOT include events — use details to get events
|
|
1054
|
+
|
|
1055
|
+
# POST — Create a schedule
|
|
1056
|
+
body = {
|
|
1057
|
+
"type": "holidays",
|
|
1058
|
+
"name": "National Holidays",
|
|
1059
|
+
"events": [
|
|
1060
|
+
{
|
|
1061
|
+
"name": "Independence Day",
|
|
1062
|
+
"startDate": "2026-07-04",
|
|
1063
|
+
"endDate": "2026-07-04",
|
|
1064
|
+
"allDayEnabled": True
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
}
|
|
1068
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/locations/{loc_id}/schedules", json=body)
|
|
1069
|
+
# Returns: {"id": "<schedule_id>"}
|
|
1070
|
+
|
|
1071
|
+
# GET — Get schedule details (includes events)
|
|
1072
|
+
# sched_type is "businessHours" or "holidays"
|
|
1073
|
+
result = api.session.rest_get(
|
|
1074
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}"
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
# PUT — Update a schedule
|
|
1078
|
+
body = {
|
|
1079
|
+
"name": "Updated Schedule Name",
|
|
1080
|
+
"events": [...] # Full event list
|
|
1081
|
+
}
|
|
1082
|
+
result = api.session.rest_put(
|
|
1083
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}",
|
|
1084
|
+
json=body
|
|
1085
|
+
)
|
|
1086
|
+
# Returns schedule ID (changes if name changed!)
|
|
1087
|
+
|
|
1088
|
+
# DELETE — Delete a schedule
|
|
1089
|
+
api.session.rest_delete(
|
|
1090
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}"
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
# --- Schedule events ---
|
|
1094
|
+
|
|
1095
|
+
# GET — Get a single event
|
|
1096
|
+
result = api.session.rest_get(
|
|
1097
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}/events/{event_id}"
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
# POST — Add an event to a schedule
|
|
1101
|
+
body = {
|
|
1102
|
+
"name": "Christmas Day",
|
|
1103
|
+
"startDate": "2026-12-25",
|
|
1104
|
+
"endDate": "2026-12-25",
|
|
1105
|
+
"allDayEnabled": True
|
|
1106
|
+
}
|
|
1107
|
+
result = api.session.rest_post(
|
|
1108
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}/events",
|
|
1109
|
+
json=body
|
|
1110
|
+
)
|
|
1111
|
+
# Returns: {"id": "<event_id>"}
|
|
1112
|
+
|
|
1113
|
+
# PUT — Update an event
|
|
1114
|
+
body = {
|
|
1115
|
+
"name": "Christmas Day 2026",
|
|
1116
|
+
"startDate": "2026-12-25",
|
|
1117
|
+
"endDate": "2026-12-25",
|
|
1118
|
+
"allDayEnabled": True,
|
|
1119
|
+
"recurrence": {
|
|
1120
|
+
"recurYearlyByDate": {"dayOfMonth": 25, "month": "DECEMBER"}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
result = api.session.rest_put(
|
|
1124
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}/events/{event_id}",
|
|
1125
|
+
json=body
|
|
1126
|
+
)
|
|
1127
|
+
# Returns event ID (changes if name changed!)
|
|
1128
|
+
|
|
1129
|
+
# DELETE — Delete an event
|
|
1130
|
+
api.session.rest_delete(
|
|
1131
|
+
f"{BASE}/telephony/config/locations/{loc_id}/schedules/{sched_type}/{sched_id}/events/{event_id}"
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
# --- User-level schedules (same pattern, different base) ---
|
|
1135
|
+
# Base: {BASE}/people/{person_id}/features/schedules/...
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
**Gotcha:** Schedule IDs are name-derived (base64-encoded from the schedule name). Renaming a schedule changes its ID -- the old ID returns 404 after rename. Always re-fetch the ID after a name change. Event IDs behave the same way.
|
|
1139
|
+
|
|
1140
|
+
**Gotcha:** The `ScheduleApi` import path is `wxc_sdk.common.schedules`, NOT `wxc_sdk.telephony.schedules`.
|
|
1141
|
+
|
|
1142
|
+
### CLI Examples
|
|
1143
|
+
|
|
1144
|
+
```bash
|
|
1145
|
+
# List all schedules for a location
|
|
1146
|
+
wxcli location-schedules list Y2lzY29zcGFyazovL_LOC_ID
|
|
1147
|
+
|
|
1148
|
+
# List only business hours schedules
|
|
1149
|
+
wxcli location-schedules list Y2lzY29zcGFyazovL_LOC_ID --type businessHours
|
|
1150
|
+
|
|
1151
|
+
# List only holiday schedules
|
|
1152
|
+
wxcli location-schedules list Y2lzY29zcGFyazovL_LOC_ID --type holidays
|
|
1153
|
+
|
|
1154
|
+
# Filter by schedule name
|
|
1155
|
+
wxcli location-schedules list Y2lzY29zcGFyazovL_LOC_ID --name "National Holidays"
|
|
1156
|
+
|
|
1157
|
+
# Get schedule details (includes events); type is "businessHours" or "holidays"
|
|
1158
|
+
wxcli location-schedules show Y2lzY29zcGFyazovL_LOC_ID holidays Y2lzY29zcGFyazovL_SCHED_ID
|
|
1159
|
+
|
|
1160
|
+
# Create a holiday schedule (events can be added later)
|
|
1161
|
+
wxcli location-schedules create Y2lzY29zcGFyazovL_LOC_ID \
|
|
1162
|
+
--type holidays --name "National Holidays"
|
|
1163
|
+
|
|
1164
|
+
# Create a business hours schedule with events via --json-body
|
|
1165
|
+
wxcli location-schedules create Y2lzY29zcGFyazovL_LOC_ID \
|
|
1166
|
+
--type businessHours --name "Office Hours" \
|
|
1167
|
+
--json-body '{
|
|
1168
|
+
"type": "businessHours",
|
|
1169
|
+
"name": "Office Hours",
|
|
1170
|
+
"events": [
|
|
1171
|
+
{"name": "Monday AM", "startDate": "2026-01-05", "endDate": "2026-01-05",
|
|
1172
|
+
"startTime": "09:00", "endTime": "12:00"}
|
|
1173
|
+
]
|
|
1174
|
+
}'
|
|
1175
|
+
|
|
1176
|
+
# Add an all-day holiday event to an existing schedule
|
|
1177
|
+
wxcli location-schedules create-events Y2lzY29zcGFyazovL_LOC_ID holidays Y2lzY29zcGFyazovL_SCHED_ID \
|
|
1178
|
+
--name "Independence Day" --start-date 2026-07-04 --end-date 2026-07-04 --all-day-enabled
|
|
1179
|
+
|
|
1180
|
+
# Add a timed event (business hours block)
|
|
1181
|
+
wxcli location-schedules create-events Y2lzY29zcGFyazovL_LOC_ID businessHours Y2lzY29zcGFyazovL_SCHED_ID \
|
|
1182
|
+
--name "Monday AM" --start-date 2026-01-05 --end-date 2026-01-05 \
|
|
1183
|
+
--start-time "09:00" --end-time "12:00"
|
|
1184
|
+
|
|
1185
|
+
# Get details for a specific event
|
|
1186
|
+
wxcli location-schedules show-events Y2lzY29zcGFyazovL_LOC_ID holidays Y2lzY29zcGFyazovL_SCHED_ID Y2lzY29zcGFyazovL_EVENT_ID
|
|
1187
|
+
|
|
1188
|
+
# Delete an event from a schedule
|
|
1189
|
+
wxcli location-schedules delete-events Y2lzY29zcGFyazovL_LOC_ID holidays Y2lzY29zcGFyazovL_SCHED_ID Y2lzY29zcGFyazovL_EVENT_ID --force
|
|
1190
|
+
|
|
1191
|
+
# Delete an entire schedule
|
|
1192
|
+
wxcli location-schedules delete Y2lzY29zcGFyazovL_LOC_ID holidays Y2lzY29zcGFyazovL_SCHED_ID --force
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
> **Note:** Schedule and event IDs are name-derived (base64-encoded). Renaming a schedule or event changes its ID -- always re-fetch the ID after a name change.
|
|
1196
|
+
|
|
1197
|
+
### 4.4 Endpoint URL Structure
|
|
1198
|
+
|
|
1199
|
+
Location schedules:
|
|
1200
|
+
```
|
|
1201
|
+
/v1/telephony/config/locations/{locationId}/schedules
|
|
1202
|
+
/v1/telephony/config/locations/{locationId}/schedules/{scheduleType}/{scheduleId}
|
|
1203
|
+
/v1/telephony/config/locations/{locationId}/schedules/{scheduleType}/{scheduleId}/events
|
|
1204
|
+
/v1/telephony/config/locations/{locationId}/schedules/{scheduleType}/{scheduleId}/events/{eventId}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
User schedules:
|
|
1208
|
+
```
|
|
1209
|
+
/v1/people/{personId}/features/schedules
|
|
1210
|
+
/v1/people/{personId}/features/schedules/{scheduleType}/{scheduleId}
|
|
1211
|
+
/v1/people/{personId}/features/schedules/{scheduleType}/{scheduleId}/events
|
|
1212
|
+
/v1/people/{personId}/features/schedules/{scheduleType}/{scheduleId}/events/{eventId}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
### 4.4 US Holidays Example (from `examples/us_holidays.py`)
|
|
1216
|
+
|
|
1217
|
+
The SDK repo includes a complete example showing how to programmatically create and maintain a "National Holidays" schedule across all US locations. Key patterns from this example:
|
|
1218
|
+
|
|
1219
|
+
**Full workflow:**
|
|
1220
|
+
|
|
1221
|
+
1. **Get US locations:** Filter `api.locations.list()` by `address.country == 'US'`.
|
|
1222
|
+
2. **Get holidays:** Uses the `calendarific` package to fetch US national holidays for a year range.
|
|
1223
|
+
3. **Check for existing schedule:** List holiday schedules filtered by name `"National Holidays"`.
|
|
1224
|
+
4. **Create or update:**
|
|
1225
|
+
- If no schedule exists, create a new one with all future holiday events.
|
|
1226
|
+
- If schedule exists, get details (to load events), delete past events, and add missing future events.
|
|
1227
|
+
|
|
1228
|
+
**Key code patterns from the example:**
|
|
1229
|
+
|
|
1230
|
+
```python
|
|
1231
|
+
# Access the schedules API
|
|
1232
|
+
ats = api.telephony.schedules
|
|
1233
|
+
|
|
1234
|
+
# List holiday schedules for a location, filter by name
|
|
1235
|
+
schedule = next(
|
|
1236
|
+
(s for s in ats.list(
|
|
1237
|
+
obj_id=location.location_id,
|
|
1238
|
+
schedule_type=ScheduleType.holidays,
|
|
1239
|
+
name='National Holidays'
|
|
1240
|
+
) if s.name == 'National Holidays'),
|
|
1241
|
+
None
|
|
1242
|
+
)
|
|
1243
|
+
|
|
1244
|
+
# Get full details (list doesn't include events)
|
|
1245
|
+
schedule = ats.details(
|
|
1246
|
+
obj_id=location.location_id,
|
|
1247
|
+
schedule_type=ScheduleType.holidays,
|
|
1248
|
+
schedule_id=schedule.schedule_id
|
|
1249
|
+
)
|
|
1250
|
+
|
|
1251
|
+
# Create an all-day holiday event
|
|
1252
|
+
event = Event(
|
|
1253
|
+
name='Independence Day 2026',
|
|
1254
|
+
start_date=date(2026, 7, 4),
|
|
1255
|
+
end_date=date(2026, 7, 4),
|
|
1256
|
+
all_day_enabled=True
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
# Create a new holiday schedule with events
|
|
1260
|
+
schedule = Schedule(
|
|
1261
|
+
name='National Holidays',
|
|
1262
|
+
schedule_type=ScheduleType.holidays,
|
|
1263
|
+
events=[event1, event2, ...]
|
|
1264
|
+
)
|
|
1265
|
+
schedule_id = ats.create(obj_id=location.location_id, schedule=schedule)
|
|
1266
|
+
|
|
1267
|
+
# Add a single event to an existing schedule
|
|
1268
|
+
event_id = ats.event_create(
|
|
1269
|
+
obj_id=location.location_id,
|
|
1270
|
+
schedule_type=ScheduleType.holidays,
|
|
1271
|
+
schedule_id=schedule.schedule_id,
|
|
1272
|
+
event=event
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
# Delete a past event
|
|
1276
|
+
ats.event_delete(
|
|
1277
|
+
obj_id=location.location_id,
|
|
1278
|
+
schedule_type=ScheduleType.holidays,
|
|
1279
|
+
schedule_id=schedule.schedule_id,
|
|
1280
|
+
event_id=event.event_id
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
# Delete an entire holiday schedule
|
|
1284
|
+
ats.delete_schedule(
|
|
1285
|
+
obj_id=location.location_id,
|
|
1286
|
+
schedule_type=ScheduleType.holidays,
|
|
1287
|
+
schedule_id=schedule.schedule_id
|
|
1288
|
+
)
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
**Threading pattern:** The example uses `ThreadPoolExecutor` with per-location locks to safely update schedules across multiple locations concurrently. Each location gets its own lock to prevent race conditions when multiple years are processed in parallel.
|
|
1292
|
+
|
|
1293
|
+
**Holiday event filtering:** Events are excluded if they fall on a Sunday or are in the past (`holiday.date >= today and holiday.date.weekday() != 6`).
|
|
1294
|
+
|
|
1295
|
+
---
|
|
1296
|
+
|
|
1297
|
+
## 5. Cross-Cutting Patterns
|
|
1298
|
+
|
|
1299
|
+
### Auth Scopes Summary
|
|
1300
|
+
|
|
1301
|
+
| Operation | Required Scope |
|
|
1302
|
+
|-----------|---------------|
|
|
1303
|
+
| Read announcements, playlists, schedules, access codes | `spark-admin:telephony_config_read` |
|
|
1304
|
+
| Write announcements, playlists, schedules, access codes | `spark-admin:telephony_config_write` |
|
|
1305
|
+
| List schedules (location) | `spark-admin:telephony_config_read` |
|
|
1306
|
+
| List schedules (user/person) | `spark-admin:people_read` |
|
|
1307
|
+
|
|
1308
|
+
### Org vs. Location Scoping
|
|
1309
|
+
|
|
1310
|
+
| Resource | Org-Level | Location-Level |
|
|
1311
|
+
|----------|-----------|----------------|
|
|
1312
|
+
| Announcements | Yes (default when `location_id` is None) | Yes (pass `location_id`) |
|
|
1313
|
+
| Playlists | Yes (created at org, assigned to locations) | No (assigned via `modify_assigned_locations`) |
|
|
1314
|
+
| Access Codes | Yes (`OrganisationAccessCodesApi`) | Yes (`LocationAccessCodesApi`) |
|
|
1315
|
+
| Schedules | No | Yes (primary), also user-level |
|
|
1316
|
+
|
|
1317
|
+
### Typical Music On Hold Setup Workflow
|
|
1318
|
+
|
|
1319
|
+
1. Upload WAV files to the announcement repository (org or location level).
|
|
1320
|
+
2. Create a playlist referencing the uploaded announcement IDs (max 25).
|
|
1321
|
+
3. Assign the playlist to target locations via `modify_assigned_locations()`.
|
|
1322
|
+
|
|
1323
|
+
### Typical Holiday Schedule Workflow
|
|
1324
|
+
|
|
1325
|
+
1. Identify target locations (e.g., filter by country).
|
|
1326
|
+
2. Check if a holiday schedule already exists for each location.
|
|
1327
|
+
3. Create the schedule with all-day events, or add events to an existing schedule.
|
|
1328
|
+
4. Periodically clean up past events and add future ones.
|
|
1329
|
+
|
|
1330
|
+
---
|
|
1331
|
+
|
|
1332
|
+
## See Also
|
|
1333
|
+
|
|
1334
|
+
- **[Call Features — Major](call-features-major.md)** — Auto Attendants, Call Queues, and Hunt Groups that consume schedules for time-based routing
|