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,1366 @@
|
|
|
1
|
+
# Provisioning Reference
|
|
2
|
+
|
|
3
|
+
User, license, and location provisioning for Webex Calling via the `wxc_sdk` Python SDK. Each section includes both typed SDK methods and **Raw HTTP** examples using `api.session.rest_*()`.
|
|
4
|
+
|
|
5
|
+
> **Prerequisite:** All examples assume a configured `WebexSimpleApi` instance. See `authentication.md` for token setup. For the raw HTTP pattern, see `archive/wxc-sdk-patterns.md` section 1.5.
|
|
6
|
+
|
|
7
|
+
## Sources
|
|
8
|
+
|
|
9
|
+
- wxc_sdk v1.30.0 (github.com/jeokrohn/wxc_sdk)
|
|
10
|
+
- OpenAPI spec: `specs/webex-admin.json` (People, Licenses, Locations, Organizations APIs)
|
|
11
|
+
- developer.webex.com People, Licenses, Locations, and Organizations APIs
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
1. [Required Scopes](#required-scopes)
|
|
18
|
+
2. [People API](#people-api)
|
|
19
|
+
3. [Licenses API](#licenses-api)
|
|
20
|
+
4. [Locations API](#locations)
|
|
21
|
+
5. [Organization API](#organization-api)
|
|
22
|
+
6. [Numbers API](#numbers)
|
|
23
|
+
7. [Provisioning Workflow](#provisioning-workflow)
|
|
24
|
+
8. [Data Models](#data-models)
|
|
25
|
+
9. [Gotchas](#gotchas) (cross-cutting)
|
|
26
|
+
10. [Bulk Cleanup / Teardown](#bulk-cleanup--teardown)
|
|
27
|
+
11. [See Also](#see-also)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Required Scopes
|
|
32
|
+
|
|
33
|
+
| Operation | Scope(s) Required |
|
|
34
|
+
|-----------|-------------------|
|
|
35
|
+
| List/view people | `spark:people_read` (own info) or `spark-admin:people_read` (all org users) |
|
|
36
|
+
| Create/update/delete people | `spark-admin:people_write` **and** `spark-admin:people_read` |
|
|
37
|
+
| List licenses | `spark-admin:licenses_read` |
|
|
38
|
+
| Assign licenses (PATCH) | `spark-admin:people_write` |
|
|
39
|
+
| List/view locations | `spark-admin:locations_read`, `spark-admin:people_read`, or `spark-admin:device_read` |
|
|
40
|
+
| Create/update/delete locations | `spark-admin:locations_write` |
|
|
41
|
+
| Delete organization | `spark-admin:organizations_write` |
|
|
42
|
+
| Enable location for calling | `spark-admin:telephony_config_write` (and likely `spark-admin:locations_write`) |
|
|
43
|
+
|
|
44
|
+
All provisioning operations require an **administrator auth token**. Non-admin tokens can only read people via `spark:people_read` with email or displayName filters.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## People API
|
|
49
|
+
|
|
50
|
+
Base path: `/v1/people`
|
|
51
|
+
|
|
52
|
+
### Listing People
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from wxc_sdk import WebexSimpleApi
|
|
56
|
+
|
|
57
|
+
api = WebexSimpleApi()
|
|
58
|
+
|
|
59
|
+
# List ALL people (admin only, no filter required)
|
|
60
|
+
all_users = list(api.people.list())
|
|
61
|
+
|
|
62
|
+
# Filter by email
|
|
63
|
+
user = next(api.people.list(email='jsmith@example.com'), None)
|
|
64
|
+
|
|
65
|
+
# Filter by display name (prefix match)
|
|
66
|
+
users = list(api.people.list(display_name='John'))
|
|
67
|
+
|
|
68
|
+
# Filter by location
|
|
69
|
+
users_at_hq = list(api.people.list(location_id='<location_id>'))
|
|
70
|
+
|
|
71
|
+
# List by IDs (up to 85)
|
|
72
|
+
users = list(api.people.list(id_list=['id1', 'id2', 'id3']))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### The `calling_data=True` Parameter (Critical)
|
|
76
|
+
|
|
77
|
+
To get calling-specific fields (`location_id`, `phone_numbers` with `work_extension` type, `extension`), you **must** pass `calling_data=True`. Without it, these fields are absent from the response.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# WITHOUT calling_data -- location_id and calling fields are missing
|
|
81
|
+
user = api.people.details(person_id='<id>')
|
|
82
|
+
print(user.location_id) # None
|
|
83
|
+
|
|
84
|
+
# WITH calling_data -- calling fields populated
|
|
85
|
+
user = api.people.details(person_id='<id>', calling_data=True)
|
|
86
|
+
print(user.location_id) # 'Y2lzY29zcGF...'
|
|
87
|
+
print(user.extension) # '1001'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This applies to `list()`, `details()`, `create()`, and `update()`.
|
|
91
|
+
|
|
92
|
+
### Identifying Calling Users
|
|
93
|
+
|
|
94
|
+
A calling user is one with a `location_id` set. The SDK examples demonstrate two approaches:
|
|
95
|
+
|
|
96
|
+
**Approach 1 -- Filter on `location_id` (synchronous)**
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# From examples/calling_users.py
|
|
100
|
+
calling_users = [user for user in api.people.list(calling_data=True)
|
|
101
|
+
if user.location_id]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Approach 2 -- Filter on calling license IDs (async)**
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# From examples/calling_users_async.py
|
|
108
|
+
calling_license_ids = set(
|
|
109
|
+
lic.license_id for lic in await api.licenses.list()
|
|
110
|
+
if lic.webex_calling
|
|
111
|
+
)
|
|
112
|
+
calling_users = [
|
|
113
|
+
user async for user in api.people.list_gen()
|
|
114
|
+
if any(lic_id in calling_license_ids for lic_id in user.licenses)
|
|
115
|
+
]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Creating a Person
|
|
119
|
+
|
|
120
|
+
At minimum, one of `displayName`, `firstName`, or `lastName` is required. For a **Webex Calling** user, you must also provide `phoneNumbers` or `extension`, `locationId`, and `licenses` in the same request.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from wxc_sdk.people import Person, PhoneNumber, PhoneNumberType
|
|
124
|
+
|
|
125
|
+
new_user = api.people.create(
|
|
126
|
+
settings=Person(
|
|
127
|
+
emails=['jsmith@example.com'],
|
|
128
|
+
display_name='John Smith',
|
|
129
|
+
first_name='John',
|
|
130
|
+
last_name='Smith',
|
|
131
|
+
licenses=['<calling_license_id>'],
|
|
132
|
+
location_id='<location_id>',
|
|
133
|
+
extension='1001',
|
|
134
|
+
phone_numbers=[PhoneNumber(type=PhoneNumberType.work, value='5551234567')]
|
|
135
|
+
),
|
|
136
|
+
calling_data=True
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Important notes on create:**
|
|
141
|
+
- A POST that returns 400 may **still have created the person**. Check with a GET before retrying.
|
|
142
|
+
- SIP addresses are assigned asynchronously -- they may not appear in the POST response. Verify with a subsequent GET.
|
|
143
|
+
- When assigning multiple licenses, the system assigns all valid ones and silently skips invalid ones.
|
|
144
|
+
|
|
145
|
+
### Updating a Person
|
|
146
|
+
|
|
147
|
+
The update is a **full PUT** -- you must include all fields, not just the changed ones. Standard pattern: GET details first, modify, then PUT.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# GET current state
|
|
151
|
+
user = api.people.details(person_id='<id>', calling_data=True)
|
|
152
|
+
|
|
153
|
+
# Modify
|
|
154
|
+
user.extension = '2002'
|
|
155
|
+
|
|
156
|
+
# PUT back
|
|
157
|
+
updated = api.people.update(person=user, calling_data=True)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Key constraints on update:**
|
|
161
|
+
- `location_id` can only be set when **first assigning** a calling license. It cannot be changed for an existing calling user.
|
|
162
|
+
- The `extension` field value should **not** include the location routing prefix. The `work_extension` phone number in the response *will* include it, but when setting `extension` on update, omit the prefix.
|
|
163
|
+
- When updating a user with multiple email addresses, the **primary email must be listed first** in the array.
|
|
164
|
+
- Some licenses are implicitly assigned by the system and cannot be removed. If you get an error about implicit licenses, make sure they remain in the `licenses` array.
|
|
165
|
+
- A PUT that returns an error **may still have partially modified** the person. Always do a GET afterward to verify current state.
|
|
166
|
+
|
|
167
|
+
### Deleting a Person
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
api.people.delete_person(person_id='<id>')
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Required roles: Full Administrator, User Administrator, or External Full Administrator.
|
|
174
|
+
|
|
175
|
+
### Get Current User
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
me = api.people.me(calling_data=True)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Raw HTTP
|
|
182
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
183
|
+
|
|
184
|
+
All People API operations can be performed via raw HTTP using `api.session.rest_*()`. This is the preferred execution pattern -- wxc_sdk handles auth and session management, while you control the exact request.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from wxc_sdk import WebexSimpleApi
|
|
188
|
+
from wxc_sdk.rest import RestError
|
|
189
|
+
|
|
190
|
+
api = WebexSimpleApi()
|
|
191
|
+
BASE = "https://webexapis.com/v1"
|
|
192
|
+
|
|
193
|
+
# ── List people ───────────────────────────────────────────────────
|
|
194
|
+
result = api.session.rest_get(f"{BASE}/people", params={
|
|
195
|
+
"max": 1000,
|
|
196
|
+
"callingData": "true", # string "true", not bool
|
|
197
|
+
})
|
|
198
|
+
people = result.get("items", [])
|
|
199
|
+
|
|
200
|
+
# ── List with filters ────────────────────────────────────────────
|
|
201
|
+
result = api.session.rest_get(f"{BASE}/people", params={
|
|
202
|
+
"email": "jsmith@example.com",
|
|
203
|
+
"callingData": "true",
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
# ── Get person details ───────────────────────────────────────────
|
|
207
|
+
person = api.session.rest_get(f"{BASE}/people/{person_id}", params={
|
|
208
|
+
"callingData": "true",
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
# ── Create a person ──────────────────────────────────────────────
|
|
212
|
+
body = {
|
|
213
|
+
"emails": ["jsmith@example.com"],
|
|
214
|
+
"displayName": "John Smith",
|
|
215
|
+
"firstName": "John",
|
|
216
|
+
"lastName": "Smith",
|
|
217
|
+
"licenses": [calling_license_id],
|
|
218
|
+
"locationId": location_id,
|
|
219
|
+
"extension": "1001",
|
|
220
|
+
"phoneNumbers": [{"type": "work", "value": "+15551234567"}],
|
|
221
|
+
}
|
|
222
|
+
result = api.session.rest_post(f"{BASE}/people", json=body)
|
|
223
|
+
new_person_id = result["id"]
|
|
224
|
+
|
|
225
|
+
# ── Update a person (full PUT -- include all fields) ─────────────
|
|
226
|
+
person = api.session.rest_get(f"{BASE}/people/{person_id}", params={
|
|
227
|
+
"callingData": "true",
|
|
228
|
+
})
|
|
229
|
+
person["extension"] = "2002"
|
|
230
|
+
api.session.rest_put(f"{BASE}/people/{person_id}", json=person)
|
|
231
|
+
|
|
232
|
+
# ── Delete a person ──────────────────────────────────────────────
|
|
233
|
+
api.session.rest_delete(f"{BASE}/people/{person_id}")
|
|
234
|
+
|
|
235
|
+
# ── Get current user ─────────────────────────────────────────────
|
|
236
|
+
me = api.session.rest_get(f"{BASE}/people/me", params={
|
|
237
|
+
"callingData": "true",
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Key differences from typed SDK:**
|
|
242
|
+
- `callingData` is a string `"true"`, not a Python bool
|
|
243
|
+
- Response key for list is `items`, not `people`
|
|
244
|
+
- No auto-pagination -- use `max=1000` for large orgs
|
|
245
|
+
- Update is a raw PUT -- you must include all fields (GET first, modify, PUT back)
|
|
246
|
+
|
|
247
|
+
### CLI Examples
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# ── List all people (table output) ────────────────────────────────
|
|
251
|
+
wxcli people list
|
|
252
|
+
|
|
253
|
+
# ── List with calling data (shows locationId, extension) ─────────
|
|
254
|
+
wxcli people list --calling-data true
|
|
255
|
+
|
|
256
|
+
# ── Search by email ───────────────────────────────────────────────
|
|
257
|
+
wxcli people list --email jsmith@example.com
|
|
258
|
+
|
|
259
|
+
# ── Search by display name (prefix match) ─────────────────────────
|
|
260
|
+
wxcli people list --display-name "John"
|
|
261
|
+
|
|
262
|
+
# ── List people at a specific location ────────────────────────────
|
|
263
|
+
wxcli people list --location-id <location_id> --calling-data true
|
|
264
|
+
|
|
265
|
+
# ── Get person details (JSON output) ──────────────────────────────
|
|
266
|
+
wxcli people show <person_id>
|
|
267
|
+
|
|
268
|
+
# ── Get person details as table ────────────────────────────────────
|
|
269
|
+
wxcli people show <person_id> -o table
|
|
270
|
+
|
|
271
|
+
# ── Get current user details ──────────────────────────────────────
|
|
272
|
+
wxcli people list-me --calling-data true
|
|
273
|
+
|
|
274
|
+
# ── Create a person ───────────────────────────────────────────────
|
|
275
|
+
wxcli people create --first-name "John" --last-name "Smith" \
|
|
276
|
+
--display-name "John Smith" --location-id <location_id> \
|
|
277
|
+
--extension "1001"
|
|
278
|
+
|
|
279
|
+
# ── Create with full JSON body (for emails, licenses, phoneNumbers)
|
|
280
|
+
wxcli people create --json-body '{
|
|
281
|
+
"emails": ["jsmith@example.com"],
|
|
282
|
+
"displayName": "John Smith",
|
|
283
|
+
"firstName": "John",
|
|
284
|
+
"lastName": "Smith",
|
|
285
|
+
"licenses": ["<calling_license_id>"],
|
|
286
|
+
"locationId": "<location_id>",
|
|
287
|
+
"extension": "1001",
|
|
288
|
+
"phoneNumbers": [{"type": "work", "value": "+15551234567"}]
|
|
289
|
+
}'
|
|
290
|
+
|
|
291
|
+
# ── Update a person ───────────────────────────────────────────────
|
|
292
|
+
wxcli people update <person_id> --extension "2002"
|
|
293
|
+
wxcli people update <person_id> --display-name "John A. Smith"
|
|
294
|
+
wxcli people update <person_id> --department "Engineering" --title "Senior Engineer"
|
|
295
|
+
|
|
296
|
+
# ── Delete a person ───────────────────────────────────────────────
|
|
297
|
+
wxcli people delete <person_id>
|
|
298
|
+
|
|
299
|
+
# ── Delete without confirmation prompt ────────────────────────────
|
|
300
|
+
wxcli people delete <person_id> --force
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**CLI notes:**
|
|
304
|
+
- `wxcli people list` defaults to table output (`-o table`); use `-o json` for full JSON.
|
|
305
|
+
- `wxcli people show` defaults to JSON output; use `-o table` for a summary view.
|
|
306
|
+
- The `--calling-data true` flag is the CLI equivalent of `calling_data=True` in the SDK. Pass it when you need calling fields (locationId, extension, phoneNumbers).
|
|
307
|
+
- For create/update operations with complex nested fields (emails, licenses, phoneNumbers), use `--json-body` with the full JSON payload.
|
|
308
|
+
- The `--limit` and `--offset` flags control client-side pagination of results.
|
|
309
|
+
|
|
310
|
+
### Gotchas
|
|
311
|
+
|
|
312
|
+
**`calling_data=True` is not optional -- it is mandatory for calling fields.**
|
|
313
|
+
The single most common mistake. Without `calling_data=True` on `list()`, `details()`, `create()`, and `update()`, the response will **not include** `location_id`, `extension`, or calling-related `phone_numbers`. Your code will see `None` for these fields and may incorrectly conclude the user is not calling-enabled.
|
|
314
|
+
|
|
315
|
+
**`location_id` is write-once for calling users.**
|
|
316
|
+
You can set `location_id` when you first assign a calling license to a user. After that, `location_id` **cannot be changed** via the People API update. The preferred path for moving a calling user to a different location is the dedicated **Move Users Job API**: `POST /v1/people/{personId}/actions/moveLocation`. This moves the user without removing and re-adding the calling license. See `User Call Settings - Validate or Initiate Move Users Job` in the API docs. As a fallback, you can remove the calling license and re-add it with the new `location_id`, but the Move Users Job API is preferred.
|
|
317
|
+
|
|
318
|
+
**The People API is a composite of multiple microservices.**
|
|
319
|
+
A create or update call can **partially succeed**. For example, the user may be created but the phone number assignment may fail (especially with invalid numbers). Always verify with a subsequent GET after errors.
|
|
320
|
+
|
|
321
|
+
**Performance limits with `calling_data=True`.**
|
|
322
|
+
The SDK enforces a soft limit of 10 users per page when fetching with `calling_data=True` (constant `MAX_USERS_WITH_CALLING_DATA = 10`). This is an **SDK internal constant**, not an API enforcement — the live API allows up to **100 results per page** with `callingData=true` (or 50 when `locationId` is also specified). Users of raw HTTP or wxcli are not subject to the SDK's 10-item constant. For large orgs using the SDK, consider the async API with `concurrent_requests` tuning.
|
|
323
|
+
|
|
324
|
+
**Extension vs. work_extension.**
|
|
325
|
+
- When **writing**: set `person.extension = '1001'` (no routing prefix).
|
|
326
|
+
- When **reading**: `phone_numbers` of type `work_extension` will have the value `<routing_prefix><extension>` (e.g., `'8001001'` where `800` is the prefix and `1001` is the extension).
|
|
327
|
+
|
|
328
|
+
**Deleting a calling user may have delayed side effects.**
|
|
329
|
+
After deleting a user, their phone numbers may not be immediately available for reassignment. The SDK test suite retries number removal with delays of up to 10 seconds between attempts when encountering 502 errors.
|
|
330
|
+
|
|
331
|
+
**`PeopleApi.create()` takes a `Person` model, not kwargs.**
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
from wxc_sdk.people import Person
|
|
335
|
+
person = Person(emails=[email], first_name=first, last_name=last)
|
|
336
|
+
result = api.people.create(settings=person)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Do NOT call `api.people.create(emails=[...], first_name=...)` — this raises `TypeError`.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Licenses API
|
|
344
|
+
|
|
345
|
+
> For full org-wide license auditing, assignment, and reclamation, see [admin-licensing.md](admin-licensing.md).
|
|
346
|
+
|
|
347
|
+
Base path: `/v1/licenses`
|
|
348
|
+
|
|
349
|
+
### Listing Licenses
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
all_licenses = api.licenses.list()
|
|
353
|
+
|
|
354
|
+
for lic in all_licenses:
|
|
355
|
+
print(f'{lic.name}: {lic.consumed_units}/{lic.total_units}')
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Finding Specific Licenses
|
|
359
|
+
|
|
360
|
+
The `License` model has convenience properties for common license types:
|
|
361
|
+
|
|
362
|
+
| Property | License Name Matched |
|
|
363
|
+
|----------|---------------------|
|
|
364
|
+
| `lic.webex_calling_professional` | `"Webex Calling - Professional"` |
|
|
365
|
+
| `lic.webex_calling_basic` | `"Webex Calling - Basic"` |
|
|
366
|
+
| `lic.webex_calling_workspaces` | `"Webex Calling - Workspaces"` |
|
|
367
|
+
| `lic.webex_calling` | Any of the above three |
|
|
368
|
+
| `lic.cx_essentials` | `"Customer Experience - Essential"` |
|
|
369
|
+
|
|
370
|
+
**Find the Webex Calling Professional license:**
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
wxc_pro_license = next(
|
|
374
|
+
(lic for lic in api.licenses.list()
|
|
375
|
+
if lic.webex_calling_professional),
|
|
376
|
+
None
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Find an available calling license (with capacity):**
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
def get_calling_license(api):
|
|
384
|
+
"""Get ID of an available calling license with remaining capacity."""
|
|
385
|
+
licenses = [
|
|
386
|
+
lic for lic in api.licenses.list()
|
|
387
|
+
if lic.webex_calling and not lic.webex_calling_workspaces
|
|
388
|
+
]
|
|
389
|
+
available = next(
|
|
390
|
+
(lic for lic in licenses
|
|
391
|
+
if lic.consumed_units < lic.total_units),
|
|
392
|
+
None
|
|
393
|
+
)
|
|
394
|
+
return available.license_id if available else None
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Find and store both Calling and UCM licenses (for migration):**
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
# From Cisco Live lab
|
|
401
|
+
wxc_pro_license = None
|
|
402
|
+
ucm_license = None
|
|
403
|
+
|
|
404
|
+
for lic in api.licenses.list():
|
|
405
|
+
if lic.name == 'Webex Calling - Professional':
|
|
406
|
+
wxc_pro_license = lic
|
|
407
|
+
if lic.name == 'Unified Communication Manager (UCM)':
|
|
408
|
+
ucm_license = lic
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### License Details
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
license_detail = api.licenses.details(license_id='<id>')
|
|
415
|
+
print(f'Name: {license_detail.name}')
|
|
416
|
+
print(f'Used: {license_detail.consumed_units}/{license_detail.total_units}')
|
|
417
|
+
print(f'By users: {license_detail.consumed_by_users}')
|
|
418
|
+
print(f'By workspaces: {license_detail.consumed_by_workspaces}')
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Listing Users Assigned to a License
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
users = list(api.licenses.assigned_users(license_id='<id>'))
|
|
425
|
+
for user in users:
|
|
426
|
+
print(f'{user.display_name} ({user.email}) - {user.type}')
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Assigning Licenses to Users (PATCH Method)
|
|
430
|
+
|
|
431
|
+
The `assign_licenses_to_users` method is the **recommended approach** for license assignment, especially for Webex Calling. It supports adding and removing licenses in a single call, and it handles calling-specific properties (location, phone number, extension).
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
from wxc_sdk.licenses import LicenseRequest, LicenseProperties, LicenseRequestOperation
|
|
435
|
+
|
|
436
|
+
# Assign a Webex Calling license with location and extension
|
|
437
|
+
api.licenses.assign_licenses_to_users(
|
|
438
|
+
person_id='<person_id>',
|
|
439
|
+
licenses=[
|
|
440
|
+
LicenseRequest(
|
|
441
|
+
id='<calling_license_id>',
|
|
442
|
+
operation=LicenseRequestOperation.add, # default is 'add'
|
|
443
|
+
properties=LicenseProperties(
|
|
444
|
+
location_id='<location_id>',
|
|
445
|
+
extension='1001'
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
]
|
|
449
|
+
)
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**With phone number instead of extension:**
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
api.licenses.assign_licenses_to_users(
|
|
456
|
+
person_id='<person_id>',
|
|
457
|
+
licenses=[
|
|
458
|
+
LicenseRequest(
|
|
459
|
+
id='<calling_license_id>',
|
|
460
|
+
properties=LicenseProperties(
|
|
461
|
+
location_id='<location_id>',
|
|
462
|
+
phone_number='+15551234567'
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
]
|
|
466
|
+
)
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Removing a license:**
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
api.licenses.assign_licenses_to_users(
|
|
473
|
+
person_id='<person_id>',
|
|
474
|
+
licenses=[
|
|
475
|
+
LicenseRequest(
|
|
476
|
+
id='<license_id_to_remove>',
|
|
477
|
+
operation=LicenseRequestOperation.remove
|
|
478
|
+
)
|
|
479
|
+
]
|
|
480
|
+
)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Combined: remove UCM + add Calling in one call:**
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
api.licenses.assign_licenses_to_users(
|
|
487
|
+
person_id='<person_id>',
|
|
488
|
+
licenses=[
|
|
489
|
+
LicenseRequest(
|
|
490
|
+
id=ucm_license.license_id,
|
|
491
|
+
operation=LicenseRequestOperation.remove
|
|
492
|
+
),
|
|
493
|
+
LicenseRequest(
|
|
494
|
+
id=wxc_pro_license.license_id,
|
|
495
|
+
operation=LicenseRequestOperation.add,
|
|
496
|
+
properties=LicenseProperties(
|
|
497
|
+
location_id='<location_id>',
|
|
498
|
+
extension='1001'
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
]
|
|
502
|
+
)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**LicenseProperties requirements for Calling licenses:**
|
|
506
|
+
- Either `phone_number` or `extension` is mandatory.
|
|
507
|
+
- If `phone_number` is not provided, then `location_id` is mandatory.
|
|
508
|
+
|
|
509
|
+
You can identify the user by either `email` or `person_id` (at least one required).
|
|
510
|
+
|
|
511
|
+
### Raw HTTP
|
|
512
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
from wxc_sdk import WebexSimpleApi
|
|
516
|
+
api = WebexSimpleApi()
|
|
517
|
+
BASE = "https://webexapis.com/v1"
|
|
518
|
+
|
|
519
|
+
# ── List all licenses ────────────────────────────────────────────
|
|
520
|
+
result = api.session.rest_get(f"{BASE}/licenses", params={"max": 1000})
|
|
521
|
+
licenses = result.get("items", [])
|
|
522
|
+
|
|
523
|
+
# ── Get license details ──────────────────────────────────────────
|
|
524
|
+
license_detail = api.session.rest_get(f"{BASE}/licenses/{license_id}")
|
|
525
|
+
|
|
526
|
+
# ── Find calling licenses ────────────────────────────────────────
|
|
527
|
+
calling_licenses = [
|
|
528
|
+
lic for lic in licenses
|
|
529
|
+
if "Webex Calling" in lic.get("name", "")
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
# ── Assign license to user (PATCH) ──────────────────────────────
|
|
533
|
+
# The PATCH endpoint for license assignment:
|
|
534
|
+
body = {
|
|
535
|
+
"email": "jsmith@example.com",
|
|
536
|
+
"licenses": [
|
|
537
|
+
{
|
|
538
|
+
"id": calling_license_id,
|
|
539
|
+
"operation": "add",
|
|
540
|
+
"properties": {
|
|
541
|
+
"locationId": location_id,
|
|
542
|
+
"extension": "1001",
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
}
|
|
547
|
+
api.session.rest_post(f"{BASE}/licenses/users", json=body)
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Note:** The Licenses API is read-only for license inventory (list/details). License assignment to users is done via the `/licenses/users` PATCH-style endpoint (implemented as POST). There is no create/update/delete for license definitions themselves.
|
|
551
|
+
|
|
552
|
+
### Gotchas
|
|
553
|
+
|
|
554
|
+
**License ID assignment requires the full base64-encoded ID.**
|
|
555
|
+
License IDs in Webex are long base64-encoded strings (e.g., `Y2lzY29zcGFyazov...`). Always retrieve them programmatically from `api.licenses.list()` -- never hardcode them, as they vary by organization and subscription.
|
|
556
|
+
|
|
557
|
+
**SCIM 2.0 is now the recommended path for user creation.**
|
|
558
|
+
As of January 2024, Webex recommends SCIM 2.0 (`wxc_sdk.scim.ScimV2Api`) over the People API for user creation and management due to higher performance and standard connectors. Users created via SCIM can then be licensed using the `assign_licenses_to_users` PATCH method.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Locations API
|
|
563
|
+
|
|
564
|
+
Base path: `/v1/locations`
|
|
565
|
+
|
|
566
|
+
### Listing Locations
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
# All locations
|
|
570
|
+
locations = list(api.locations.list())
|
|
571
|
+
|
|
572
|
+
# Filter by name (case-insensitive contains match)
|
|
573
|
+
locations = list(api.locations.list(name='headquarters'))
|
|
574
|
+
|
|
575
|
+
# Filter by ID
|
|
576
|
+
locations = list(api.locations.list(location_id='<id>'))
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Find Location by Exact Name
|
|
580
|
+
|
|
581
|
+
The SDK provides a convenience method not available in the raw API:
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
location = api.locations.by_name('Pod18')
|
|
585
|
+
if location:
|
|
586
|
+
print(f'Found: {location.name} (ID: {location.location_id})')
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
This iterates the list internally and matches on exact name equality.
|
|
590
|
+
|
|
591
|
+
### Location Details
|
|
592
|
+
|
|
593
|
+
```python
|
|
594
|
+
location = api.locations.details(location_id='<id>')
|
|
595
|
+
print(f'Name: {location.name}')
|
|
596
|
+
print(f'Address: {location.address.address1}, {location.address.city}, {location.address.state}')
|
|
597
|
+
print(f'Timezone: {location.time_zone}')
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Creating a Location
|
|
601
|
+
|
|
602
|
+
All of the following parameters are **required**:
|
|
603
|
+
- `name`
|
|
604
|
+
- `time_zone`
|
|
605
|
+
- `preferred_language`
|
|
606
|
+
- `announcement_language`
|
|
607
|
+
- `address1`, `city`, `state`, `postal_code`, `country`
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
location_id = api.locations.create(
|
|
611
|
+
name='San Jose Office',
|
|
612
|
+
time_zone='America/Los_Angeles',
|
|
613
|
+
preferred_language='en_us',
|
|
614
|
+
announcement_language='en_us',
|
|
615
|
+
address1='123 Main St',
|
|
616
|
+
city='San Jose',
|
|
617
|
+
state='CA',
|
|
618
|
+
postal_code='95113',
|
|
619
|
+
country='US'
|
|
620
|
+
)
|
|
621
|
+
print(f'Created location: {location_id}')
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Optional parameters: `address2`, `latitude`, `longitude`, `notes`.
|
|
625
|
+
|
|
626
|
+
**Name length constraint:** While the API supports up to 256 characters, locations that will be **enabled for Webex Calling** must have names with a maximum of **80 characters**.
|
|
627
|
+
|
|
628
|
+
The return value is the new location's ID string.
|
|
629
|
+
|
|
630
|
+
### Updating a Location
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
location = api.locations.details(location_id='<id>')
|
|
634
|
+
location.name = 'San Jose HQ'
|
|
635
|
+
api.locations.update(location_id=location.location_id, settings=location)
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
The same 80-character name limit applies if the location is calling-enabled.
|
|
639
|
+
|
|
640
|
+
### Deleting a Location
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
api.locations.delete(location_id='<id>')
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Prerequisite:** Webex Calling must be **disabled** for the location before it can be deleted.
|
|
647
|
+
|
|
648
|
+
### Enabling a Location for Webex Calling
|
|
649
|
+
|
|
650
|
+
Creating a location via the Locations API does **not** automatically enable it for Webex Calling. You must use the separate Location Call Settings API:
|
|
651
|
+
|
|
652
|
+
```python
|
|
653
|
+
# SDK method:
|
|
654
|
+
api.telephony.location.enable_for_calling(location_id='<id>', ...)
|
|
655
|
+
|
|
656
|
+
# Raw HTTP equivalent:
|
|
657
|
+
api.session.rest_post( # POST — not PUT; URL path should be verified against live API reference
|
|
658
|
+
f"{BASE}/telephony/config/locations/{loc_id}",
|
|
659
|
+
json={"announcementLanguage": "en_us"}, # lowercase required
|
|
660
|
+
)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
The `announcement_language` field is **required** when enabling a location for Webex Calling. It must be **lowercase** (`en_us`, not `en_US`) -- see gotchas #13 and #14.
|
|
664
|
+
|
|
665
|
+
### Raw HTTP
|
|
666
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
667
|
+
|
|
668
|
+
```python
|
|
669
|
+
from wxc_sdk import WebexSimpleApi
|
|
670
|
+
from wxc_sdk.rest import RestError
|
|
671
|
+
|
|
672
|
+
api = WebexSimpleApi()
|
|
673
|
+
BASE = "https://webexapis.com/v1"
|
|
674
|
+
|
|
675
|
+
# ── List locations ───────────────────────────────────────────────
|
|
676
|
+
result = api.session.rest_get(f"{BASE}/locations", params={"max": 1000})
|
|
677
|
+
locations = result.get("items", [])
|
|
678
|
+
|
|
679
|
+
# ── List with name filter ────────────────────────────────────────
|
|
680
|
+
result = api.session.rest_get(f"{BASE}/locations", params={
|
|
681
|
+
"name": "headquarters",
|
|
682
|
+
"max": 1000,
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
# ── Get location details ─────────────────────────────────────────
|
|
686
|
+
location = api.session.rest_get(f"{BASE}/locations/{loc_id}")
|
|
687
|
+
|
|
688
|
+
# ── Create a location ────────────────────────────────────────────
|
|
689
|
+
body = {
|
|
690
|
+
"name": "San Jose Office",
|
|
691
|
+
"timeZone": "America/Los_Angeles",
|
|
692
|
+
"preferredLanguage": "en_us",
|
|
693
|
+
"announcementLanguage": "en_us",
|
|
694
|
+
"address": {
|
|
695
|
+
"address1": "123 Main St",
|
|
696
|
+
"city": "San Jose",
|
|
697
|
+
"state": "CA",
|
|
698
|
+
"postalCode": "95113",
|
|
699
|
+
"country": "US",
|
|
700
|
+
},
|
|
701
|
+
}
|
|
702
|
+
result = api.session.rest_post(f"{BASE}/locations", json=body)
|
|
703
|
+
new_loc_id = result["id"]
|
|
704
|
+
|
|
705
|
+
# ── Update a location ────────────────────────────────────────────
|
|
706
|
+
location = api.session.rest_get(f"{BASE}/locations/{loc_id}")
|
|
707
|
+
location["name"] = "San Jose HQ"
|
|
708
|
+
api.session.rest_put(f"{BASE}/locations/{loc_id}", json=location)
|
|
709
|
+
|
|
710
|
+
# ── Delete a location ────────────────────────────────────────────
|
|
711
|
+
# WARNING: Calling-enabled locations return 409 -- see gotcha #15
|
|
712
|
+
api.session.rest_delete(f"{BASE}/locations/{loc_id}")
|
|
713
|
+
|
|
714
|
+
# ── Enable location for Webex Calling ────────────────────────────
|
|
715
|
+
# This is a SEPARATE telephony endpoint, not the Locations API
|
|
716
|
+
body = {
|
|
717
|
+
"announcementLanguage": "en_us", # MUST be lowercase -- see gotcha #13
|
|
718
|
+
}
|
|
719
|
+
api.session.rest_post( # POST — not PUT; URL path should be verified against live API reference
|
|
720
|
+
f"{BASE}/telephony/config/locations/{loc_id}",
|
|
721
|
+
json=body,
|
|
722
|
+
)
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
**Raw HTTP gotchas for locations:**
|
|
726
|
+
- `announcementLanguage` must be **lowercase** (`en_us` not `en_US`) when calling `enable_for_calling` -- the telephony backend rejects mixed case with "Invalid Language Code" (gotcha #13)
|
|
727
|
+
- `announcementLanguage` returns `None` from the locations details endpoint even when set -- always set it explicitly before enabling calling (gotcha #14)
|
|
728
|
+
- Calling-enabled locations **cannot be deleted via API** -- returns `409 Conflict: Location is being referenced, cannot be deleted`. Must use Control Hub (gotcha #15)
|
|
729
|
+
- The `safe_delete_check` response uses field `locationDeleteStatus` (not `status`), with value `"UNBLOCKED"` or `"BLOCKED"` (gotcha #17)
|
|
730
|
+
- The `address` field in raw HTTP is a nested object with `address1`, `city`, `state`, `postalCode`, `country`. The SDK flattens these to top-level kwargs in `locations.create()`.
|
|
731
|
+
|
|
732
|
+
### CLI Examples
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
# ── List all locations ────────────────────────────────────────────
|
|
736
|
+
wxcli locations list
|
|
737
|
+
|
|
738
|
+
# ── Filter locations by name (case-insensitive contains match) ────
|
|
739
|
+
wxcli locations list --name "headquarters"
|
|
740
|
+
|
|
741
|
+
# ── List locations as JSON ────────────────────────────────────────
|
|
742
|
+
wxcli locations list -o json
|
|
743
|
+
|
|
744
|
+
# ── Get location details ──────────────────────────────────────────
|
|
745
|
+
wxcli locations show <location_id>
|
|
746
|
+
|
|
747
|
+
# ── Get location details as table ─────────────────────────────────
|
|
748
|
+
wxcli locations show <location_id> -o table
|
|
749
|
+
|
|
750
|
+
# ── Create a location (all required fields) ───────────────────────
|
|
751
|
+
wxcli locations create \
|
|
752
|
+
--name "San Jose Office" \
|
|
753
|
+
--time-zone "America/Los_Angeles" \
|
|
754
|
+
--preferred-language "en_us" \
|
|
755
|
+
--announcement-language "en_us" \
|
|
756
|
+
--json-body '{
|
|
757
|
+
"address": {
|
|
758
|
+
"address1": "123 Main St",
|
|
759
|
+
"city": "San Jose",
|
|
760
|
+
"state": "CA",
|
|
761
|
+
"postalCode": "95113",
|
|
762
|
+
"country": "US"
|
|
763
|
+
}
|
|
764
|
+
}'
|
|
765
|
+
|
|
766
|
+
# ── Update a location ────────────────────────────────────────────
|
|
767
|
+
wxcli locations update <location_id> --name "San Jose HQ"
|
|
768
|
+
wxcli locations update <location_id> --time-zone "America/New_York"
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**CLI notes:**
|
|
772
|
+
- The `locations` group covers both REST CRUD (list, create, show, update, delete) and floors management.
|
|
773
|
+
- `wxcli locations create` requires `--name`, `--time-zone`, `--preferred-language`, and `--announcement-language`. The address must be passed via `--json-body` since it is a nested object.
|
|
774
|
+
- Use lowercase language codes (e.g., `en_us` not `en_US`) for `--announcement-language` to avoid "Invalid Language Code" errors when later enabling calling.
|
|
775
|
+
- Location names must be 80 characters or fewer if the location will be enabled for Webex Calling.
|
|
776
|
+
|
|
777
|
+
### Floors
|
|
778
|
+
|
|
779
|
+
Locations support floor management for workspace organization:
|
|
780
|
+
|
|
781
|
+
```python
|
|
782
|
+
# List floors
|
|
783
|
+
floors = api.locations.list_floors(location_id='<id>')
|
|
784
|
+
|
|
785
|
+
# Create a floor
|
|
786
|
+
floor = api.locations.create_floor(location_id='<id>', floor_number=3, display_name='3rd Floor')
|
|
787
|
+
|
|
788
|
+
# Get floor details
|
|
789
|
+
floor = api.locations.floor_details(location_id='<id>', floor_id='<floor_id>')
|
|
790
|
+
|
|
791
|
+
# Update a floor
|
|
792
|
+
floor.display_name = 'Third Floor - Engineering'
|
|
793
|
+
api.locations.update_floor(floor=floor)
|
|
794
|
+
|
|
795
|
+
# Delete a floor
|
|
796
|
+
api.locations.delete_floor(location_id='<id>', floor_id='<floor_id>')
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Gotchas
|
|
800
|
+
|
|
801
|
+
**Location must exist before user assignment.**
|
|
802
|
+
You cannot assign a user to a location that does not exist. Create the location first, optionally enable it for calling, then assign users.
|
|
803
|
+
|
|
804
|
+
**Location name length for calling.**
|
|
805
|
+
Locations enabled for Webex Calling must have names of **80 characters or fewer**. The general Locations API allows 256, but calling features and Control Hub enforce the shorter limit.
|
|
806
|
+
|
|
807
|
+
**`enable_for_calling` requires lowercase language codes.**
|
|
808
|
+
The telephony `enable_for_calling` API rejects `en_US` (mixed case) for `announcement_language` with error `Invalid Language Code`. Use `en_us` (all lowercase). The general Locations API stores `preferredLanguage` as `en_US` but the telephony backend expects lowercase.
|
|
809
|
+
|
|
810
|
+
```python
|
|
811
|
+
location = api.locations.details(location_id=loc_id)
|
|
812
|
+
if not location.announcement_language:
|
|
813
|
+
location.announcement_language = (location.preferred_language or "en_US").lower()
|
|
814
|
+
api.telephony.location.enable_for_calling(location=location)
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
**`announcement_language` returns None from details endpoint.**
|
|
818
|
+
`LocationsApi.details()` returns `announcement_language = None` even on locations that have it set. This is a Webex API inconsistency. Always set it explicitly before calling `enable_for_calling`.
|
|
819
|
+
|
|
820
|
+
**Cannot delete calling-enabled locations via API.**
|
|
821
|
+
`LocationsApi.delete()` returns `409 Conflict: Location is being referenced, cannot be deleted` for any location with Webex Calling enabled. There is **no API to disable calling on a location** — `wxcadm` confirms: "There is currently no way to delete a Location outside of Control Hub." The `safe_delete_check_before_disabling_calling_location` precheck may return `UNBLOCKED` but the delete still fails due to the telephony reference.
|
|
822
|
+
|
|
823
|
+
Calling-enabled locations can only be deleted from Control Hub.
|
|
824
|
+
|
|
825
|
+
**`SafeDeleteCheckResponse` uses `location_delete_status`, not `status`.**
|
|
826
|
+
The response model field is `location_delete_status` (value: `"UNBLOCKED"` or `"BLOCKED"`), not `status`. The `blocking` field contains a model with `users_in_use_count`, `trunks_in_use_count`, etc.
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
## Organization API
|
|
831
|
+
|
|
832
|
+
Base path: `/v1/organizations`
|
|
833
|
+
|
|
834
|
+
### Listing Organizations
|
|
835
|
+
|
|
836
|
+
```python
|
|
837
|
+
orgs = api.organizations.list()
|
|
838
|
+
|
|
839
|
+
# With XSI (BroadSoft) endpoint data
|
|
840
|
+
orgs = api.organizations.list(calling_data=True)
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Organization Details
|
|
844
|
+
|
|
845
|
+
```python
|
|
846
|
+
org = api.organizations.details(org_id='<id>', calling_data=True)
|
|
847
|
+
print(f'Name: {org.display_name}')
|
|
848
|
+
print(f'XSI Actions: {org.xsi_actions_endpoint}')
|
|
849
|
+
print(f'XSI Events: {org.xsi_events_endpoint}')
|
|
850
|
+
print(f'XSI Domain: {org.xsi_domain}')
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
The `calling_data=True` parameter returns XSI (BroadSoft) endpoint values:
|
|
854
|
+
- `xsi_actions_endpoint` -- base path to xsi-actions
|
|
855
|
+
- `xsi_events_endpoint` -- base path to xsi-events
|
|
856
|
+
- `xsi_events_channel_endpoint` -- base path to xsi-events-channel
|
|
857
|
+
- `xsi_domain` -- api-prepended bcBaseDomain for the org
|
|
858
|
+
|
|
859
|
+
### Deleting an Organization
|
|
860
|
+
|
|
861
|
+
```python
|
|
862
|
+
api.organizations.delete(org_id='<id>')
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Requires authorization from a user with the **Full Administrator Role**. Deletion may take up to 10 minutes to complete after the response returns.
|
|
866
|
+
|
|
867
|
+
### Raw HTTP
|
|
868
|
+
|
|
869
|
+
```python
|
|
870
|
+
from wxc_sdk import WebexSimpleApi
|
|
871
|
+
api = WebexSimpleApi()
|
|
872
|
+
BASE = "https://webexapis.com/v1"
|
|
873
|
+
|
|
874
|
+
# ── List organizations ──────────────────────────────────────────
|
|
875
|
+
result = api.session.rest_get(f"{BASE}/organizations", params={"max": 1000})
|
|
876
|
+
orgs = result.get("items", [])
|
|
877
|
+
|
|
878
|
+
# ── List with calling data (XSI endpoints) ─────────────────────
|
|
879
|
+
result = api.session.rest_get(f"{BASE}/organizations", params={
|
|
880
|
+
"callingData": "true",
|
|
881
|
+
"max": 1000,
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
# ── Get organization details ────────────────────────────────────
|
|
885
|
+
org = api.session.rest_get(f"{BASE}/organizations/{org_id}", params={
|
|
886
|
+
"callingData": "true",
|
|
887
|
+
})
|
|
888
|
+
# XSI fields: xsiActionsEndpoint, xsiEventsEndpoint,
|
|
889
|
+
# xsiEventsChannelEndpoint, xsiDomain
|
|
890
|
+
|
|
891
|
+
# ── Delete an organization ──────────────────────────────────────
|
|
892
|
+
# WARNING: Requires Full Administrator Role. Deletion takes up to 10 min.
|
|
893
|
+
api.session.rest_delete(f"{BASE}/organizations/{org_id}")
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
**Key differences from typed SDK:**
|
|
897
|
+
- `callingData` is a string `"true"`, not a Python bool
|
|
898
|
+
- Response key for list is `items`, not `organizations`
|
|
899
|
+
- XSI field names are camelCase in raw JSON: `xsiActionsEndpoint`, `xsiEventsEndpoint`, `xsiEventsChannelEndpoint`, `xsiDomain`
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Numbers API
|
|
904
|
+
|
|
905
|
+
Base path: `/v1/telephony/config/numbers` (list) and `/v1/telephony/config/locations/{locationId}/numbers` (add/activate/remove)
|
|
906
|
+
|
|
907
|
+
Numbers must be added to a location's inventory before they can be assigned to users or workspaces. The Numbers API manages the phone number lifecycle: adding numbers to locations, activating/deactivating them, and removing them.
|
|
908
|
+
|
|
909
|
+
### CLI Examples
|
|
910
|
+
|
|
911
|
+
```bash
|
|
912
|
+
# ── List all phone numbers in the org ─────────────────────────────
|
|
913
|
+
wxcli numbers list
|
|
914
|
+
|
|
915
|
+
# ── List numbers for a specific location ──────────────────────────
|
|
916
|
+
wxcli numbers list --location-id <location_id>
|
|
917
|
+
|
|
918
|
+
# ── Search for a specific phone number ────────────────────────────
|
|
919
|
+
wxcli numbers list --phone-number "+15551234567"
|
|
920
|
+
|
|
921
|
+
# ── List only available (unassigned) numbers ──────────────────────
|
|
922
|
+
wxcli numbers list --available true
|
|
923
|
+
|
|
924
|
+
# ── Filter by number state ────────────────────────────────────────
|
|
925
|
+
wxcli numbers list --state ACTIVE
|
|
926
|
+
wxcli numbers list --state INACTIVE
|
|
927
|
+
|
|
928
|
+
# ── Filter by owner type ──────────────────────────────────────────
|
|
929
|
+
wxcli numbers list --owner-type PEOPLE
|
|
930
|
+
wxcli numbers list --owner-type PLACE
|
|
931
|
+
|
|
932
|
+
# ── Filter by number type ────────────────────────────────────────
|
|
933
|
+
wxcli numbers list --number-type NUMBER
|
|
934
|
+
wxcli numbers list --number-type EXTENSION
|
|
935
|
+
|
|
936
|
+
# ── List numbers as JSON ──────────────────────────────────────────
|
|
937
|
+
wxcli numbers list -o json
|
|
938
|
+
|
|
939
|
+
# ── Add phone numbers to a location ───────────────────────────────
|
|
940
|
+
wxcli numbers create <location_id> --json-body '{
|
|
941
|
+
"phoneNumbers": ["+15551234567", "+15551234568"]
|
|
942
|
+
}'
|
|
943
|
+
|
|
944
|
+
# ── Activate numbers in a location ────────────────────────────────
|
|
945
|
+
wxcli numbers update <location_id> --action ACTIVATE --json-body '{
|
|
946
|
+
"phoneNumbers": ["+15551234567"]
|
|
947
|
+
}'
|
|
948
|
+
|
|
949
|
+
# ── Deactivate numbers in a location ──────────────────────────────
|
|
950
|
+
wxcli numbers update <location_id> --action DEACTIVATE --json-body '{
|
|
951
|
+
"phoneNumbers": ["+15551234567"]
|
|
952
|
+
}'
|
|
953
|
+
|
|
954
|
+
# ── Remove phone numbers from a location ──────────────────────────
|
|
955
|
+
wxcli numbers delete <location_id>
|
|
956
|
+
|
|
957
|
+
# ── Remove without confirmation prompt ────────────────────────────
|
|
958
|
+
wxcli numbers delete <location_id> --force
|
|
959
|
+
|
|
960
|
+
# ── Validate phone numbers before adding ──────────────────────────
|
|
961
|
+
wxcli numbers validate-phone-numbers --json-body '{
|
|
962
|
+
"phoneNumbers": ["+15551234567", "+15551234568"]
|
|
963
|
+
}'
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
**CLI notes:**
|
|
967
|
+
- The `numbers` group covers number CRUD, validation, and manage-numbers jobs.
|
|
968
|
+
- `wxcli numbers list` returns org-wide numbers by default. Use `--location-id` to scope to a specific location.
|
|
969
|
+
- Adding and activating numbers requires `--json-body` with a `phoneNumbers` array since the CLI cannot flatten nested array body fields into flags.
|
|
970
|
+
- Numbers must be added to a location (`create`) and activated (`update --action ACTIVATE`) before they can be assigned to users.
|
|
971
|
+
- Use `validate-phone-numbers` to check number validity before adding them to avoid errors.
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
## Provisioning Workflow
|
|
976
|
+
|
|
977
|
+
Step-by-step process to enable a user for Webex Calling. Based on the Cisco Live provisioning lab and SDK test patterns.
|
|
978
|
+
|
|
979
|
+
### Method A: Using People API (update licenses on Person object)
|
|
980
|
+
|
|
981
|
+
This approach manipulates the `licenses` array directly on the Person object.
|
|
982
|
+
|
|
983
|
+
```python
|
|
984
|
+
from wxc_sdk import WebexSimpleApi
|
|
985
|
+
|
|
986
|
+
api = WebexSimpleApi()
|
|
987
|
+
|
|
988
|
+
# ── Step 1: Find the required licenses ──────────────────────────────
|
|
989
|
+
wxc_pro_license = None
|
|
990
|
+
ucm_license = None
|
|
991
|
+
|
|
992
|
+
for lic in api.licenses.list():
|
|
993
|
+
if lic.name == 'Webex Calling - Professional':
|
|
994
|
+
wxc_pro_license = lic
|
|
995
|
+
if lic.name == 'Unified Communication Manager (UCM)':
|
|
996
|
+
ucm_license = lic
|
|
997
|
+
|
|
998
|
+
print(f'WxC Pro: {wxc_pro_license.license_id}')
|
|
999
|
+
print(f'UCM: {ucm_license.license_id if ucm_license else "not found"}')
|
|
1000
|
+
|
|
1001
|
+
# ── Step 2: Find the target location ────────────────────────────────
|
|
1002
|
+
location = api.locations.by_name('San Jose Office')
|
|
1003
|
+
print(f'Location: {location.name} (ID: {location.location_id})')
|
|
1004
|
+
|
|
1005
|
+
# ── Step 3: Get the user's full details ─────────────────────────────
|
|
1006
|
+
user = next(api.people.list(email='jsmith@example.com'), None)
|
|
1007
|
+
user_details = api.people.details(person_id=user.person_id, calling_data=True)
|
|
1008
|
+
|
|
1009
|
+
# ── Step 4: Update licenses and location ────────────────────────────
|
|
1010
|
+
# Add Webex Calling Professional license
|
|
1011
|
+
if wxc_pro_license.license_id not in user_details.licenses:
|
|
1012
|
+
user_details.licenses.append(wxc_pro_license.license_id)
|
|
1013
|
+
|
|
1014
|
+
# Remove UCM license (if migrating from on-prem)
|
|
1015
|
+
if ucm_license and ucm_license.license_id in user_details.licenses:
|
|
1016
|
+
user_details.licenses.remove(ucm_license.license_id)
|
|
1017
|
+
|
|
1018
|
+
# Set location and extension
|
|
1019
|
+
user_details.location_id = location.location_id
|
|
1020
|
+
user_details.extension = '1001'
|
|
1021
|
+
|
|
1022
|
+
# ── Step 5: Push the update ─────────────────────────────────────────
|
|
1023
|
+
updated_user = api.people.update(person=user_details, calling_data=True)
|
|
1024
|
+
|
|
1025
|
+
# ── Step 6: Verify ──────────────────────────────────────────────────
|
|
1026
|
+
verified = api.people.details(person_id=updated_user.person_id, calling_data=True)
|
|
1027
|
+
assert verified.location_id is not None
|
|
1028
|
+
assert wxc_pro_license.license_id in verified.licenses
|
|
1029
|
+
print(f'User {verified.display_name} enabled for calling at {location.name}')
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
### Method B: Using Licenses PATCH API (recommended for new provisioning)
|
|
1033
|
+
|
|
1034
|
+
This approach uses the dedicated `assign_licenses_to_users` PATCH endpoint, which is cleaner for new license assignments and supports calling-specific properties natively.
|
|
1035
|
+
|
|
1036
|
+
```python
|
|
1037
|
+
from wxc_sdk import WebexSimpleApi
|
|
1038
|
+
from wxc_sdk.licenses import LicenseRequest, LicenseProperties
|
|
1039
|
+
from wxc_sdk.people import Person
|
|
1040
|
+
|
|
1041
|
+
api = WebexSimpleApi()
|
|
1042
|
+
|
|
1043
|
+
# ── Step 1: Find the calling license ────────────────────────────────
|
|
1044
|
+
calling_license_id = next(
|
|
1045
|
+
(lic.license_id for lic in api.licenses.list()
|
|
1046
|
+
if lic.webex_calling_professional
|
|
1047
|
+
and lic.consumed_units < lic.total_units),
|
|
1048
|
+
None
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
# ── Step 2: Find the target location ────────────────────────────────
|
|
1052
|
+
location = api.locations.by_name('San Jose Office')
|
|
1053
|
+
|
|
1054
|
+
# ── Step 3: Create the user (if new) ────────────────────────────────
|
|
1055
|
+
new_user = api.people.create(
|
|
1056
|
+
settings=Person(
|
|
1057
|
+
emails=['jsmith@example.com'],
|
|
1058
|
+
display_name='John Smith',
|
|
1059
|
+
first_name='John',
|
|
1060
|
+
last_name='Smith'
|
|
1061
|
+
)
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
# ── Step 4: Assign calling license with location and extension ──────
|
|
1065
|
+
response = api.licenses.assign_licenses_to_users(
|
|
1066
|
+
person_id=new_user.person_id,
|
|
1067
|
+
licenses=[
|
|
1068
|
+
LicenseRequest(
|
|
1069
|
+
id=calling_license_id,
|
|
1070
|
+
properties=LicenseProperties(
|
|
1071
|
+
location_id=location.location_id,
|
|
1072
|
+
extension='1001'
|
|
1073
|
+
)
|
|
1074
|
+
)
|
|
1075
|
+
]
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# ── Step 5: Verify ──────────────────────────────────────────────────
|
|
1079
|
+
verified = api.people.details(person_id=new_user.person_id, calling_data=True)
|
|
1080
|
+
assert calling_license_id in verified.licenses
|
|
1081
|
+
assert verified.location_id is not None
|
|
1082
|
+
print(f'License response: {response}')
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
### Method B is preferred when:
|
|
1086
|
+
- You are provisioning net-new users (create user, then assign license separately).
|
|
1087
|
+
- You need to set calling-specific properties (`phone_number`, `extension`, `location_id`) as part of the license assignment in one atomic call.
|
|
1088
|
+
- You are bulk-provisioning users created via SCIM 2.0.
|
|
1089
|
+
|
|
1090
|
+
### Method A is preferred when:
|
|
1091
|
+
- You are migrating existing users (e.g., removing UCM, adding WxC).
|
|
1092
|
+
- You need to modify other Person fields at the same time as license changes.
|
|
1093
|
+
|
|
1094
|
+
---
|
|
1095
|
+
|
|
1096
|
+
## Data Models
|
|
1097
|
+
|
|
1098
|
+
### Person
|
|
1099
|
+
|
|
1100
|
+
| Field | Type | Notes |
|
|
1101
|
+
|-------|------|-------|
|
|
1102
|
+
| `person_id` | `str` | Unique ID (aliased from `id` in JSON) |
|
|
1103
|
+
| `emails` | `list[str]` | Currently only one email supported |
|
|
1104
|
+
| `phone_numbers` | `list[PhoneNumber]` | Requires `calling_data=True` to populate |
|
|
1105
|
+
| `extension` | `str` | Webex Calling extension (no routing prefix). Requires `calling_data=True` |
|
|
1106
|
+
| `location_id` | `str` | Calling location. Requires `calling_data=True`. Only settable on initial calling license assignment |
|
|
1107
|
+
| `display_name` | `str` | Full name |
|
|
1108
|
+
| `first_name` | `str` | |
|
|
1109
|
+
| `last_name` | `str` | |
|
|
1110
|
+
| `org_id` | `str` | Organization |
|
|
1111
|
+
| `licenses` | `list[str]` | License IDs assigned to user |
|
|
1112
|
+
| `roles` | `list[str]` | Role IDs |
|
|
1113
|
+
| `department` | `str` | Business department |
|
|
1114
|
+
| `manager` | `str` | Manager identifier |
|
|
1115
|
+
| `manager_id` | `str` | Manager's Person ID |
|
|
1116
|
+
| `title` | `str` | Job title |
|
|
1117
|
+
| `addresses` | `list[PersonAddress]` | Postal addresses |
|
|
1118
|
+
| `site_urls` | `list[str]` | Webex Meetings site URLs |
|
|
1119
|
+
| `timezone` | `str` | User's timezone |
|
|
1120
|
+
| `status` | `PeopleStatus` | Presence status (org members only) |
|
|
1121
|
+
| `invite_pending` | `bool` | Awaiting activation (admin-visible only) |
|
|
1122
|
+
| `login_enabled` | `bool` | Can use Webex (admin-visible only) |
|
|
1123
|
+
| `person_type` | `PersonType` | `person`, `bot`, or `appuser` |
|
|
1124
|
+
| `sip_addresses` | `list[SipAddress]` | Read-only SIP addresses |
|
|
1125
|
+
| `created` | `datetime` | Read-only |
|
|
1126
|
+
| `last_modified` | `datetime` | Read-only |
|
|
1127
|
+
|
|
1128
|
+
**Helper properties on Person:**
|
|
1129
|
+
- `person_id_uuid` -- Person ID decoded to UUID format
|
|
1130
|
+
- `plus_e164` -- List of +E.164 phone numbers
|
|
1131
|
+
- `tn` -- First +E.164 phone number (primary TN)
|
|
1132
|
+
|
|
1133
|
+
### PhoneNumber
|
|
1134
|
+
|
|
1135
|
+
| Field | Type | Values |
|
|
1136
|
+
|-------|------|--------|
|
|
1137
|
+
| `number_type` | `PhoneNumberType` | `work`, `work_extension`, `mobile`, `fax`, `enterprise`, `alternate1`, `alternate2` |
|
|
1138
|
+
| `value` | `str` | The phone number or extension value |
|
|
1139
|
+
| `primary` | `bool` | Whether this is the primary number |
|
|
1140
|
+
|
|
1141
|
+
Note: `work_extension` values include the location **routing prefix** prepended to the extension. When setting `extension` on a Person, do **not** include the routing prefix.
|
|
1142
|
+
|
|
1143
|
+
### License
|
|
1144
|
+
|
|
1145
|
+
| Field | Type | Notes |
|
|
1146
|
+
|-------|------|-------|
|
|
1147
|
+
| `license_id` | `str` | Unique ID (aliased from `id`) |
|
|
1148
|
+
| `name` | `str` | Human-readable license name |
|
|
1149
|
+
| `total_units` | `int` | Total allocated |
|
|
1150
|
+
| `consumed_units` | `int` | Total consumed |
|
|
1151
|
+
| `consumed_by_users` | `int` | Consumed by users |
|
|
1152
|
+
| `consumed_by_workspaces` | `int` | Consumed by workspaces |
|
|
1153
|
+
| `subscription_id` | `str` | Associated subscription |
|
|
1154
|
+
| `site_url` | `str` | Webex Meetings site (if applicable) |
|
|
1155
|
+
| `site_type` | `SiteType` | `Control Hub managed site`, `Linked site`, or `Site Admin managed site` |
|
|
1156
|
+
|
|
1157
|
+
**Helper properties on License:**
|
|
1158
|
+
- `webex_calling` -- True if any calling license type
|
|
1159
|
+
- `webex_calling_professional` -- True if name is `"Webex Calling - Professional"`
|
|
1160
|
+
- `webex_calling_basic` -- True if name is `"Webex Calling - Basic"`
|
|
1161
|
+
- `webex_calling_workspaces` -- True if name is `"Webex Calling - Workspaces"`
|
|
1162
|
+
- `cx_essentials` -- True if name is `"Customer Experience - Essential"`
|
|
1163
|
+
|
|
1164
|
+
> **Note — "Basic" vs. "Standard" tier naming:** The `"Webex Calling - Basic"` name matched by `lic.webex_calling_basic` may be stale for newer orgs. Cisco's current tier naming uses **Standard** (not Basic). Any code doing exact string matching on `"Webex Calling - Basic"` may miss Standard-tier licenses. The SDK property `lic.webex_calling_basic` is an internal convenience name and may still resolve correctly depending on SDK version, but verify the actual license name string returned by `GET /v1/licenses` in your org before relying on exact string matches.
|
|
1165
|
+
|
|
1166
|
+
### LicenseRequest (for PATCH assignment)
|
|
1167
|
+
|
|
1168
|
+
| Field | Type | Notes |
|
|
1169
|
+
|-------|------|-------|
|
|
1170
|
+
| `id` | `str` | License ID to add/remove |
|
|
1171
|
+
| `operation` | `LicenseRequestOperation` | `add` (default) or `remove` |
|
|
1172
|
+
| `properties` | `LicenseProperties` | Location, phone, extension (for calling licenses) |
|
|
1173
|
+
|
|
1174
|
+
### LicenseProperties
|
|
1175
|
+
|
|
1176
|
+
| Field | Type | Notes |
|
|
1177
|
+
|-------|------|-------|
|
|
1178
|
+
| `location_id` | `str` | Required if `phone_number` not provided |
|
|
1179
|
+
| `phone_number` | `str` | Work phone number (E.164) |
|
|
1180
|
+
| `extension` | `str` | Webex Calling extension |
|
|
1181
|
+
|
|
1182
|
+
Either `phone_number` or `extension` is mandatory for Calling license assignment. If `phone_number` is omitted, `location_id` is mandatory.
|
|
1183
|
+
|
|
1184
|
+
### Location
|
|
1185
|
+
|
|
1186
|
+
| Field | Type | Notes |
|
|
1187
|
+
|-------|------|-------|
|
|
1188
|
+
| `location_id` | `str` | Unique ID (aliased from `id`) |
|
|
1189
|
+
| `name` | `str` | Max 80 chars if calling-enabled |
|
|
1190
|
+
| `org_id` | `str` | Organization |
|
|
1191
|
+
| `address` | `LocationAddress` | Physical address |
|
|
1192
|
+
| `time_zone` | `str` | IANA timezone |
|
|
1193
|
+
| `preferred_language` | `str` | Default email language |
|
|
1194
|
+
| `announcement_language` | `str` | Phone announcement language. Required for calling enablement |
|
|
1195
|
+
| `latitude` | `float` | |
|
|
1196
|
+
| `longitude` | `float` | |
|
|
1197
|
+
| `notes` | `str` | |
|
|
1198
|
+
|
|
1199
|
+
### LocationAddress
|
|
1200
|
+
|
|
1201
|
+
| Field | Type |
|
|
1202
|
+
|-------|------|
|
|
1203
|
+
| `address1` | `str` |
|
|
1204
|
+
| `address2` | `str` |
|
|
1205
|
+
| `city` | `str` |
|
|
1206
|
+
| `state` | `str` |
|
|
1207
|
+
| `postal_code` | `str` |
|
|
1208
|
+
| `country` | `str` (ISO-3166 2-letter code) |
|
|
1209
|
+
|
|
1210
|
+
### Organization
|
|
1211
|
+
|
|
1212
|
+
| Field | Type | Notes |
|
|
1213
|
+
|-------|------|-------|
|
|
1214
|
+
| `org_id` | `str` | Unique ID (aliased from `id`) |
|
|
1215
|
+
| `display_name` | `str` | Organization name |
|
|
1216
|
+
| `created` | `datetime` | Creation timestamp |
|
|
1217
|
+
| `xsi_actions_endpoint` | `str` | BroadSoft XSI actions path. Requires `calling_data=True` |
|
|
1218
|
+
| `xsi_events_endpoint` | `str` | BroadSoft XSI events path. Requires `calling_data=True` |
|
|
1219
|
+
| `xsi_events_channel_endpoint` | `str` | XSI events channel path. Requires `calling_data=True` |
|
|
1220
|
+
| `xsi_domain` | `str` | api-prepended bcBaseDomain. Requires `calling_data=True` |
|
|
1221
|
+
|
|
1222
|
+
---
|
|
1223
|
+
|
|
1224
|
+
## Gotchas
|
|
1225
|
+
|
|
1226
|
+
Cross-cutting gotchas that span multiple API surfaces. Section-specific gotchas are inline within their respective sections above.
|
|
1227
|
+
|
|
1228
|
+
### 403 errors on license or people calls
|
|
1229
|
+
|
|
1230
|
+
Usually means the access token does not have admin scopes. Verify the token has `spark-admin:people_read`, `spark-admin:people_write`, and `spark-admin:licenses_read`.
|
|
1231
|
+
|
|
1232
|
+
### Phone numbers must be provisioned to the location first
|
|
1233
|
+
|
|
1234
|
+
Before assigning a DID/TN to a user, the number must already be added to the location's number inventory via the telephony location numbers API:
|
|
1235
|
+
|
|
1236
|
+
```python
|
|
1237
|
+
api.telephony.location.number.add(
|
|
1238
|
+
location_id='<location_id>',
|
|
1239
|
+
phone_numbers=['+15551234567']
|
|
1240
|
+
)
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
### Numbers list API returns key `phoneNumbers`, not `numbers`
|
|
1244
|
+
|
|
1245
|
+
`GET /telephony/config/numbers` returns a response body with the key `phoneNumbers`, not `numbers`. Code that looks for `response['numbers']` will get a `KeyError`.
|
|
1246
|
+
|
|
1247
|
+
### Workspaces list API returns key `items`, not `workspaces`
|
|
1248
|
+
|
|
1249
|
+
`GET /workspaces` returns a response body with the key `items`, not `workspaces`. Parse using `response['items']` when working with raw API responses.
|
|
1250
|
+
|
|
1251
|
+
### Manage numbers jobs list API returns key `items`, not `manageNumbers`
|
|
1252
|
+
|
|
1253
|
+
The manage numbers jobs list endpoint returns its results under the key `items`, not `manageNumbers`. This is inconsistent with other telephony job endpoints.
|
|
1254
|
+
|
|
1255
|
+
### Manage numbers job body uses `numberList` array, not `phoneNumbers`
|
|
1256
|
+
|
|
1257
|
+
The manage numbers job creation body expects a `numberList` array where each element contains `locationId` and `numbers`. Do not use `phoneNumbers` as the key -- the API will reject or ignore it.
|
|
1258
|
+
|
|
1259
|
+
### Location creation is two steps: create + enable calling
|
|
1260
|
+
|
|
1261
|
+
`POST /v1/locations` creates the location but does NOT enable Webex Calling. You must separately call `POST /v1/telephony/config/locations` with the location's `id`, `name`, `timeZone`, `preferredLanguage`, `announcementLanguage`, and `address`. Without this second call, assigning calling-licensed users to the location fails with "Calling flag not set".
|
|
1262
|
+
|
|
1263
|
+
### Calling user creation requires extension or phone number
|
|
1264
|
+
|
|
1265
|
+
`POST /v1/people?callingData=true` with a calling license and location requires either `extension` or `phoneNumbers` in the body. The API rejects with "Create Calling user either Phone number or Extension is required" if neither is provided. You cannot create a calling user first and assign an extension separately — it must be done atomically.
|
|
1266
|
+
|
|
1267
|
+
### User create with callingData=false may silently create the user
|
|
1268
|
+
|
|
1269
|
+
If `POST /v1/people?callingData=false` fails with 400 (e.g., "Calling flag not set"), the user may have already been created without calling configuration. A subsequent retry returns 409 Conflict. Always check `GET /v1/people?email=...` before retrying user creation. If the user exists, use `PUT` to update with calling data instead.
|
|
1270
|
+
|
|
1271
|
+
### Number porting has no public API
|
|
1272
|
+
|
|
1273
|
+
Number port-in requests, LOA submission, porting status tracking, and new number ordering from Cisco Calling Plan are all done through the Control Hub UI or via Cisco's PTS (PSTN Technical Support) team. The Numbers API (`wxcli numbers`) only manages numbers *after* they are ported in or provisioned — it cannot initiate a port.
|
|
1274
|
+
|
|
1275
|
+
### Location deletion via API is unreliable for calling-enabled locations
|
|
1276
|
+
|
|
1277
|
+
Locations with Webex Calling enabled cannot be deleted directly via the public
|
|
1278
|
+
API (`409 Conflict: Location is being referenced`). The older guidance to
|
|
1279
|
+
"disable calling first" via a `wxcli location-call-settings update-location-calling`
|
|
1280
|
+
command is stale for this repo:
|
|
1281
|
+
|
|
1282
|
+
- that command does **not** exist in the current CLI
|
|
1283
|
+
- the public API does **not** expose a reliable "disable calling on location"
|
|
1284
|
+
operation for general teardown workflows
|
|
1285
|
+
|
|
1286
|
+
What you *can* do with CLI/API:
|
|
1287
|
+
1. Delete all location-scoped resources first (virtual lines, call parks, hunt groups, call queues, schedules, trunks, devices, workspaces, users)
|
|
1288
|
+
2. Run `wxcli location-settings safe-delete-check LOCATION_ID` to inspect visible blockers
|
|
1289
|
+
3. Retry `wxcli locations delete --force LOCATION_ID`
|
|
1290
|
+
|
|
1291
|
+
What may still require Control Hub:
|
|
1292
|
+
- final disable/delete of a calling-enabled location, even after all visible
|
|
1293
|
+
dependencies have been removed via API
|
|
1294
|
+
|
|
1295
|
+
Practical operator rule: use CLI/API to clear dependencies, but warn the user
|
|
1296
|
+
that the final location removal may still need to be completed in Control Hub.
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
## Bulk Cleanup / Teardown
|
|
1301
|
+
|
|
1302
|
+
When tearing down resources programmatically (e.g., cleaning up after a stress test or migration dry run), resources must be deleted in **reverse dependency order**. Deleting in the wrong order produces 409 (Conflict) or 400 (reference still exists) errors.
|
|
1303
|
+
|
|
1304
|
+
### Deletion Order (top-to-bottom)
|
|
1305
|
+
|
|
1306
|
+
| Step | Resource Type | wxcli Delete Command | Notes |
|
|
1307
|
+
|------|--------------|---------------------|-------|
|
|
1308
|
+
| 1 | Dial Plans | `wxcli call-routing delete --force {id}` | Just `delete`, not `delete-dial-plan` |
|
|
1309
|
+
| 2 | Route Lists | `wxcli call-routing delete-route-lists --force {id}` | Plural suffix |
|
|
1310
|
+
| 3 | Route Groups | `wxcli call-routing delete-route-groups --force {id}` | Plural suffix |
|
|
1311
|
+
| 4 | Translation Patterns | `wxcli call-routing delete-translation-patterns-call-routing --force {id}` | |
|
|
1312
|
+
| 5 | Trunks | `wxcli call-routing delete-trunks --force {id}` | Check for dial plan refs first (Error 27349) |
|
|
1313
|
+
| 6 | Call Queues | `wxcli call-queue delete --force {locationId} {id}` | Needs locationId |
|
|
1314
|
+
| 7 | Hunt Groups | `wxcli hunt-group delete --force {locationId} {id}` | Needs locationId |
|
|
1315
|
+
| 8 | Auto Attendants | `wxcli auto-attendant delete --force {locationId} {id}` | Needs locationId |
|
|
1316
|
+
| 9 | Paging Groups | `wxcli paging-group delete --force {locationId} {id}` | Needs locationId |
|
|
1317
|
+
| 10 | Call Parks | `wxcli call-park delete --force {locationId} {id}` | **Must list per-location** (see below) |
|
|
1318
|
+
| 11 | Call Pickups | `wxcli call-pickup delete --force {locationId} {id}` | **Must list per-location** (see below) |
|
|
1319
|
+
| 12 | Virtual Lines | Raw `DELETE /v1/telephony/config/virtualLines/{id}` | See VL bug below |
|
|
1320
|
+
| 13 | Workspaces | `wxcli workspaces delete --force {id}` | |
|
|
1321
|
+
| 14 | Users | `wxcli users delete --force {id}` | |
|
|
1322
|
+
| 15 | Schedules | `wxcli location-schedules delete --force {locationId} {type} {scheduleId}` | |
|
|
1323
|
+
| 16 | Locations | `wxcli locations delete --force {id}` | Clear blockers first; final delete of a calling-enabled location may still require Control Hub |
|
|
1324
|
+
|
|
1325
|
+
### Key Behaviors
|
|
1326
|
+
|
|
1327
|
+
**Always use `--force` on delete commands** to skip the `[y/N]` confirmation prompt that blocks non-interactive execution.
|
|
1328
|
+
|
|
1329
|
+
**Call Parks and Call Pickups must be listed per-location.** `wxcli call-park list` and `wxcli call-pickup list` without a location argument return empty even when resources exist. You must iterate over each location:
|
|
1330
|
+
```bash
|
|
1331
|
+
for LOC_ID in $(wxcli locations list -o json | jq -r '.[].id // empty'); do
|
|
1332
|
+
wxcli call-park list "$LOC_ID" -o json
|
|
1333
|
+
wxcli call-pickup list "$LOC_ID" -o json
|
|
1334
|
+
done
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
**Virtual Line ID type mismatch bug:** The Numbers API returns virtual line owners with `VIRTUAL_LINE`-encoded IDs, but `wxcli virtual-extensions delete` sends them to the `/virtualExtensions/` endpoint which expects `VIRTUAL_EXTENSION`-encoded IDs. This always fails with 400 "Wrong type of Webex ID provided". Workaround: use raw HTTP `DELETE /v1/telephony/config/virtualLines/{id}` with a bearer token. Discover VL IDs from `wxcli numbers list -o json` (owner field) — `wxcli virtual-extensions list` may return empty.
|
|
1338
|
+
|
|
1339
|
+
**Location deletion cooldown:** Even after disabling calling and deleting all sub-resources, location DELETE may return 409 for minutes to hours. Preferred recovery: **re-invoke `wxcli cleanup run` (it is idempotent and resumes from where it left off)** — do NOT write an inline Python/bash `time.sleep` polling loop inside a single Bash tool call. The Bash tool has a ~10-minute hard timeout and long loops die silently mid-wait, leaving partial state. If bespoke retry is required, split into **discrete Bash tool calls** (one per location per attempt) with the sleep **between** tool calls, cap total wall time at ≤3 minutes, and if 409s persist, report them and stop rather than looping. See `.claude/skills/teardown/SKILL.md` → "Rule: never hand-roll polling loops".
|
|
1340
|
+
|
|
1341
|
+
**CCP-integrated PSTN gotcha (dCloud / Cisco Calling Plan orgs):** On orgs using Cisco Calling Plan, number deletion returns `ERR.V.TRM.TMN60004` ("DELETE number is supported only for non-integrated CCP") because phone-number lifecycle is owned by the PSTN portal, not the API. After trunk/route-group teardown, location DELETE then 409s with "being referenced" indefinitely — not because of locally visible resources but because Webex's internal PSTN backend is async-releasing trunk references. This typically clears in **1-4 hours** and no API action can unblock it. `wxcli cleanup run` detects this signature in two places:
|
|
1342
|
+
1. **Numbers:** a `ERR.V.TRM.TMN60004` response is logged as `[number=<ext/e164>] skipped — CCP-integrated, managed via PSTN portal` and treated as a clean skip, not a failure.
|
|
1343
|
+
2. **Locations:** if the 409 body contains `ERR.V.TRM.TMN60004`, OR says "being referenced" while a pre-check finds no local dependencies (users, workspaces, devices, features, trunks, route groups), the retry loop short-circuits and the cleanup exits 0 with a dedicated footer: `N locations blocked by CCP backend — retry in a few hours`. Re-invoke `wxcli cleanup run` later — the command is idempotent and picks up where it left off.
|
|
1344
|
+
|
|
1345
|
+
**Trunk deletion requires no remaining references:** Error 27349 names the referencing dial plan in the message.
|
|
1346
|
+
|
|
1347
|
+
### API Format Differences for Members
|
|
1348
|
+
|
|
1349
|
+
Different features use different agent/member formats in POST bodies:
|
|
1350
|
+
|
|
1351
|
+
| Feature | Field | Format | Example |
|
|
1352
|
+
|---------|-------|--------|---------|
|
|
1353
|
+
| Hunt Group | `agents` | Array of objects | `[{"id": "person_id", "weight": 50}]` |
|
|
1354
|
+
| Call Queue | `agents` | Array of objects | `[{"id": "person_id"}]` |
|
|
1355
|
+
| Call Pickup | `agents` | Array of strings | `["person_id_1", "person_id_2"]` |
|
|
1356
|
+
| Paging Group | `targets`, `originators` | Array of strings | `["person_id_1"]` |
|
|
1357
|
+
|
|
1358
|
+
Using the wrong format (e.g., `[{"id": ...}]` for pickup) produces 400 "Invalid field value: agents".
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
## See Also
|
|
1363
|
+
|
|
1364
|
+
- **`authentication.md`** — Token setup, OAuth flows, and scope reference.
|
|
1365
|
+
- **`archive/wxc-sdk-patterns.md`** — Async bulk provisioning patterns (recipes 5.3, 5.4), workspace provisioning (recipe 5.12). Archived: historical SDK doc.
|
|
1366
|
+
- **`location-calling-core.md`** — Location calling enablement and location-level telephony configuration.
|