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,2304 @@
|
|
|
1
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
2
|
+
# Call Routing & PSTN Reference
|
|
3
|
+
|
|
4
|
+
## Sources
|
|
5
|
+
- wxc_sdk v1.30.0
|
|
6
|
+
- OpenAPI spec: specs/webex-cloud-calling.json
|
|
7
|
+
- developer.webex.com Call Routing APIs
|
|
8
|
+
|
|
9
|
+
Comprehensive reference for Webex Calling dial plans, trunks, route groups, route lists, translation patterns, PSTN configuration, and call routing validation using the `wxc_sdk` and raw HTTP via `api.session`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Architecture Overview](#architecture-overview)
|
|
16
|
+
2. [SDK Access Paths](#sdk-access-paths)
|
|
17
|
+
3. [Required Scopes](#required-scopes)
|
|
18
|
+
4. [Dial Plans](#dial-plans)
|
|
19
|
+
5. [Trunks](#trunks)
|
|
20
|
+
6. [Route Groups](#route-groups)
|
|
21
|
+
7. [Route Lists](#route-lists)
|
|
22
|
+
8. [Translation Patterns](#translation-patterns)
|
|
23
|
+
9. [PSTN Configuration](#pstn-configuration)
|
|
24
|
+
10. [Premises PSTN](#premises-pstn)
|
|
25
|
+
11. [Private Network Connect (PNC)](#private-network-connect-pnc)
|
|
26
|
+
12. [Route Choices](#route-choices)
|
|
27
|
+
13. [Call Routing Test](#call-routing-test)
|
|
28
|
+
14. [Phone Number Management](#phone-number-management)
|
|
29
|
+
15. [Data Models Quick Reference](#data-models-quick-reference)
|
|
30
|
+
16. [Common Gotchas](#common-gotchas)
|
|
31
|
+
17. [See Also](#see-also)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Architecture Overview
|
|
36
|
+
|
|
37
|
+
Webex Calling routes outbound calls from cloud-hosted users to on-premises or PSTN destinations through a layered chain:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Dial Plan (pattern match)
|
|
41
|
+
|
|
|
42
|
+
v
|
|
43
|
+
Route Choice (trunk or route group)
|
|
44
|
+
|
|
|
45
|
+
+---> Trunk (direct) -----> Local Gateway / SBC -----> PSTN / PBX
|
|
46
|
+
|
|
|
47
|
+
+---> Route Group ---------> Trunk(s) with priority/failover
|
|
48
|
+
|
|
|
49
|
+
v
|
|
50
|
+
Route List (number patterns for cloud PSTN / Dedicated Instance)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### How the chain works
|
|
54
|
+
|
|
55
|
+
1. **Dial Plans** are configured globally (org-wide, not per-location). Each dial plan contains one or more **dial patterns** (e.g., `+1919!`, `9XXX`) and is associated with a single **routing choice** -- either a trunk or a route group.
|
|
56
|
+
2. When a user dials a number, the platform matches it against all dial plan patterns. The matching dial plan's routing choice determines where the call goes.
|
|
57
|
+
3. A **Trunk** is a direct SIP connection between Webex Calling and an on-premises local gateway or SBC.
|
|
58
|
+
4. A **Route Group** bundles up to 10 trunks (from different locations) with priority-based failover.
|
|
59
|
+
5. A **Route List** is a list of phone numbers reachable via a route group. Route lists provide cloud PSTN connectivity to Webex Calling Dedicated Instance.
|
|
60
|
+
6. **Translation Patterns** manipulate dialed digits before routing (outbound calls only). They can be applied at the organization level or location level.
|
|
61
|
+
7. **PSTN Connection** settings at the location level determine which PSTN provider (Cisco PSTN, Cloud-Connected PSTN, or Local Gateway) handles calls for that location.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## SDK Access Paths
|
|
66
|
+
|
|
67
|
+
All call routing APIs are accessed through the `TelephonyApi` instance on the SDK:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from wxc_sdk import WebexSimpleApi
|
|
71
|
+
|
|
72
|
+
api = WebexSimpleApi(tokens='...')
|
|
73
|
+
|
|
74
|
+
# Premises PSTN sub-APIs
|
|
75
|
+
api.telephony.prem_pstn.dial_plan # DialPlanApi
|
|
76
|
+
api.telephony.prem_pstn.trunk # TrunkApi
|
|
77
|
+
api.telephony.prem_pstn.route_group # RouteGroupApi
|
|
78
|
+
api.telephony.prem_pstn.route_list # RouteListApi
|
|
79
|
+
|
|
80
|
+
# Call routing sub-APIs
|
|
81
|
+
api.telephony.call_routing.tp # TranslationPatternsApi
|
|
82
|
+
|
|
83
|
+
# PSTN location connection
|
|
84
|
+
api.telephony.pstn # PSTNApi
|
|
85
|
+
|
|
86
|
+
# Private Network Connect
|
|
87
|
+
api.telephony.pnc # PrivateNetworkConnectApi
|
|
88
|
+
|
|
89
|
+
# Top-level telephony methods
|
|
90
|
+
api.telephony.route_choices(...)
|
|
91
|
+
api.telephony.test_call_routing(...)
|
|
92
|
+
api.telephony.validate_phone_numbers(...)
|
|
93
|
+
api.telephony.phone_numbers(...)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### API base paths
|
|
97
|
+
|
|
98
|
+
| API Class | REST Base Path |
|
|
99
|
+
|-----------|---------------|
|
|
100
|
+
| `DialPlanApi` | `telephony/config/premisePstn/dialPlans` |
|
|
101
|
+
| `TrunkApi` | `telephony/config/premisePstn/trunks` |
|
|
102
|
+
| `RouteGroupApi` | `telephony/config/premisePstn/routeGroups` |
|
|
103
|
+
| `RouteListApi` | `telephony/config/premisePstn/routeLists` |
|
|
104
|
+
| `TranslationPatternsApi` | `telephony/config/callRouting/translationPatterns` |
|
|
105
|
+
| `PSTNApi` | `telephony/pstn/locations` |
|
|
106
|
+
| `PrivateNetworkConnectApi` | `telephony/config/locations` |
|
|
107
|
+
| `PremisePstnApi` (parent) | `telephony/config/premisePstn` |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Required Scopes
|
|
112
|
+
|
|
113
|
+
| Operation | Scope |
|
|
114
|
+
|-----------|-------|
|
|
115
|
+
| Read dial plans, trunks, route groups, route lists, translation patterns | `spark-admin:telephony_config_read` |
|
|
116
|
+
| Create/update/delete dial plans, trunks, route groups, route lists, translation patterns | `spark-admin:telephony_config_write` |
|
|
117
|
+
| Test call routing | `spark-admin:telephony_config_write` |
|
|
118
|
+
| Validate phone numbers | `spark-admin:telephony_config_write` |
|
|
119
|
+
| Read phone numbers | `spark-admin:telephony_config_read` |
|
|
120
|
+
| Read PSTN location connection | `spark-admin:telephony_pstn_read` |
|
|
121
|
+
| Configure PSTN location connection | `spark-admin:telephony_pstn_write` |
|
|
122
|
+
| Read/update Private Network Connect | `spark-admin:telephony_config_read` / `spark-admin:telephony_config_write` |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Dial Plans
|
|
127
|
+
|
|
128
|
+
Dial plans route calls to on-premises destinations by use of trunks or route groups. They are configured **globally for an enterprise** and apply to all users, regardless of location.
|
|
129
|
+
|
|
130
|
+
### Data Models
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
class DialPlan(ApiModel):
|
|
134
|
+
dial_plan_id: Optional[str] # alias 'id'
|
|
135
|
+
name: Optional[str]
|
|
136
|
+
route_id: str # ID of trunk or route group
|
|
137
|
+
route_name: Optional[str]
|
|
138
|
+
route_type: RouteType # 'ROUTE_GROUP' | 'TRUNK'
|
|
139
|
+
customer: Optional[Customer]
|
|
140
|
+
|
|
141
|
+
class RouteType(str, Enum):
|
|
142
|
+
route_group = 'ROUTE_GROUP'
|
|
143
|
+
trunk = 'TRUNK'
|
|
144
|
+
cisco_pstn = 'CISCO_PSTN'
|
|
145
|
+
cloud_connected_pstn = 'CLOUD_CONNECTED_PSTN'
|
|
146
|
+
|
|
147
|
+
class CreateResponse(ApiModel):
|
|
148
|
+
dial_plan_id: str # alias 'id'
|
|
149
|
+
dial_pattern_errors: list[DialPatternValidate]
|
|
150
|
+
# .ok property: True if no errors
|
|
151
|
+
|
|
152
|
+
class PatternAndAction(ApiModel):
|
|
153
|
+
dial_pattern: str
|
|
154
|
+
action: PatternAction # 'ADD' | 'DELETE'
|
|
155
|
+
# helper statics: PatternAndAction.add(pattern), PatternAndAction.delete(pattern)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Dial Pattern Rules
|
|
159
|
+
|
|
160
|
+
- A dial pattern is a sequence of digits (1-9), followed by optional wildcard characters.
|
|
161
|
+
- `!` matches any sequence of digits. Can only occur once at the end. Only valid in E.164 patterns.
|
|
162
|
+
- `X` matches a single digit (0-9).
|
|
163
|
+
- Example E.164 pattern: `+1408!` matches any number starting with +1408.
|
|
164
|
+
- Example short pattern: `9XXX` matches any 4-digit string starting with 9.
|
|
165
|
+
|
|
166
|
+
### API Methods
|
|
167
|
+
|
|
168
|
+
#### List Dial Plans
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
DialPlanApi.list(
|
|
172
|
+
dial_plan_name: str = None,
|
|
173
|
+
route_group_name: str = None,
|
|
174
|
+
trunk_name: str = None,
|
|
175
|
+
order: str = None, # sort fields: name, routeName, routeType
|
|
176
|
+
org_id: str = None
|
|
177
|
+
) -> Generator[DialPlan, None, None]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Create Dial Plan
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
DialPlanApi.create(
|
|
184
|
+
name: str,
|
|
185
|
+
route_id: str,
|
|
186
|
+
route_type: RouteType,
|
|
187
|
+
dial_patterns: List[str] = None,
|
|
188
|
+
org_id: str = None
|
|
189
|
+
) -> CreateResponse
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Returns a `CreateResponse` with the new dial plan ID and any pattern validation errors. Check `response.ok` to confirm no errors.
|
|
193
|
+
|
|
194
|
+
#### Get Dial Plan Details
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
DialPlanApi.details(
|
|
198
|
+
dial_plan_id: str,
|
|
199
|
+
org_id: str = None
|
|
200
|
+
) -> DialPlan
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Update Dial Plan
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
DialPlanApi.update(
|
|
207
|
+
update: DialPlan, # name, route_id, route_type required
|
|
208
|
+
org_id: str = None
|
|
209
|
+
) -> None
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
All three fields (`name`, `route_id`, `route_type`) must be set on the `update` object.
|
|
213
|
+
|
|
214
|
+
#### Delete Dial Plan
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
DialPlanApi.delete_dial_plan(
|
|
218
|
+
dial_plan_id: str,
|
|
219
|
+
org_id: str = None
|
|
220
|
+
) -> None
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### List Dial Patterns
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
DialPlanApi.patterns(
|
|
227
|
+
dial_plan_id: str,
|
|
228
|
+
org_id: str = None,
|
|
229
|
+
dial_pattern: str = None
|
|
230
|
+
) -> Generator[str, None, None]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Returns the raw pattern strings.
|
|
234
|
+
|
|
235
|
+
#### Modify Dial Patterns (Add/Delete)
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
DialPlanApi.modify_patterns(
|
|
239
|
+
dial_plan_id: str,
|
|
240
|
+
dial_patterns: List[PatternAndAction],
|
|
241
|
+
org_id: str = None
|
|
242
|
+
) -> None
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Patterns not present in the request are not modified. Use `PatternAndAction.add(pattern)` and `PatternAndAction.delete(pattern)` helper methods.
|
|
246
|
+
|
|
247
|
+
#### Delete All Dial Patterns
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
DialPlanApi.delete_all_patterns(
|
|
251
|
+
dial_plan_id: str,
|
|
252
|
+
org_id: str = None
|
|
253
|
+
) -> None
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Validate Dial Patterns
|
|
257
|
+
|
|
258
|
+
This method is on the parent `PremisePstnApi`:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
PremisePstnApi.validate_pattern(
|
|
262
|
+
dial_patterns: Union[str, List[str]],
|
|
263
|
+
org_id: str = None
|
|
264
|
+
) -> DialPatternValidationResult
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
class DialPatternValidationResult(ApiModel):
|
|
269
|
+
status: ValidationStatus # 'OK' | 'ERRORS'
|
|
270
|
+
dial_pattern_status: list[DialPatternValidate]
|
|
271
|
+
# .ok property: True if status == 'OK'
|
|
272
|
+
|
|
273
|
+
class DialPatternValidate(ApiModel):
|
|
274
|
+
dial_pattern: str
|
|
275
|
+
pattern_status: DialPatternStatus # 'INVALID' | 'DUPLICATE' | 'DUPLICATE_IN_LIST'
|
|
276
|
+
message: str
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Usage Example
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from wxc_sdk.common import RouteType
|
|
283
|
+
|
|
284
|
+
# Create a dial plan pointing to a trunk
|
|
285
|
+
response = api.telephony.prem_pstn.dial_plan.create(
|
|
286
|
+
name='US-Outbound',
|
|
287
|
+
route_id=trunk_id,
|
|
288
|
+
route_type=RouteType.trunk,
|
|
289
|
+
dial_patterns=['+1!']
|
|
290
|
+
)
|
|
291
|
+
if not response.ok:
|
|
292
|
+
print(f"Pattern errors: {response.dial_pattern_errors}")
|
|
293
|
+
|
|
294
|
+
# Add patterns later
|
|
295
|
+
from wxc_sdk.telephony.prem_pstn.dial_plan import PatternAndAction
|
|
296
|
+
|
|
297
|
+
api.telephony.prem_pstn.dial_plan.modify_patterns(
|
|
298
|
+
dial_plan_id=response.dial_plan_id,
|
|
299
|
+
dial_patterns=[
|
|
300
|
+
PatternAndAction.add('+44!'),
|
|
301
|
+
PatternAndAction.delete('+1!')
|
|
302
|
+
]
|
|
303
|
+
)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### CLI Examples
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
# List all dial plans
|
|
310
|
+
wxcli call-routing list-dial-plans
|
|
311
|
+
|
|
312
|
+
# Filter dial plans by name
|
|
313
|
+
wxcli call-routing list-dial-plans --dial-plan-name "US-Outbound"
|
|
314
|
+
|
|
315
|
+
# Filter by trunk name
|
|
316
|
+
wxcli call-routing list-dial-plans --trunk-name "HQ-LGW-01"
|
|
317
|
+
|
|
318
|
+
# Filter by route group name
|
|
319
|
+
wxcli call-routing list-dial-plans --route-group-name "US-East-RG"
|
|
320
|
+
|
|
321
|
+
# Get dial plan details
|
|
322
|
+
wxcli call-routing show-dial-plans Y2lzY29zcGFyazovL_DIAL_PLAN_ID
|
|
323
|
+
|
|
324
|
+
# Create a dial plan (patterns require --json-body since dialPatterns is an array)
|
|
325
|
+
wxcli call-routing create --name "US-Outbound" --route-id Y2lzY29zcGFyazovL_TRUNK_ID
|
|
326
|
+
|
|
327
|
+
# Create with dial patterns via --json-body
|
|
328
|
+
wxcli call-routing create --json-body '{
|
|
329
|
+
"name": "US-Outbound",
|
|
330
|
+
"routeId": "Y2lzY29zcGFyazovL_TRUNK_ID",
|
|
331
|
+
"routeType": "TRUNK",
|
|
332
|
+
"dialPatterns": ["+1!", "+44!"]
|
|
333
|
+
}'
|
|
334
|
+
|
|
335
|
+
# Update a dial plan name or route assignment
|
|
336
|
+
wxcli call-routing update-dial-plans Y2lzY29zcGFyazovL_DIAL_PLAN_ID \
|
|
337
|
+
--name "US-Outbound-v2" --route-id Y2lzY29zcGFyazovL_NEW_TRUNK_ID
|
|
338
|
+
|
|
339
|
+
# Modify dial patterns (add/delete patterns on an existing dial plan)
|
|
340
|
+
wxcli call-routing update Y2lzY29zcGFyazovL_DIAL_PLAN_ID --json-body '{
|
|
341
|
+
"dialPatterns": [
|
|
342
|
+
{"dialPattern": "+44!", "action": "ADD"},
|
|
343
|
+
{"dialPattern": "+1!", "action": "DELETE"}
|
|
344
|
+
]
|
|
345
|
+
}'
|
|
346
|
+
|
|
347
|
+
# Delete all dial patterns from a dial plan
|
|
348
|
+
wxcli call-routing update Y2lzY29zcGFyazovL_DIAL_PLAN_ID --delete-all-dial-patterns
|
|
349
|
+
|
|
350
|
+
# Validate dial patterns before creating a dial plan
|
|
351
|
+
wxcli call-routing validate-a-dial --json-body '{"dialPatterns": ["+1408!", "+44!", "9XXX"]}'
|
|
352
|
+
|
|
353
|
+
# Delete a dial plan
|
|
354
|
+
wxcli call-routing delete Y2lzY29zcGFyazovL_DIAL_PLAN_ID --force
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Raw HTTP
|
|
358
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
359
|
+
|
|
360
|
+
All dial plan endpoints live under the `/premisePstn/` prefix -- NOT `/dialPlans` at the top level.
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
from wxc_sdk import WebexSimpleApi
|
|
364
|
+
api = WebexSimpleApi()
|
|
365
|
+
BASE = "https://webexapis.com/v1"
|
|
366
|
+
|
|
367
|
+
# List dial plans
|
|
368
|
+
plans = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/dialPlans",
|
|
369
|
+
params={"max": 1000})
|
|
370
|
+
# plans["dialPlans"] -> list of dicts
|
|
371
|
+
|
|
372
|
+
# Create dial plan
|
|
373
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/dialPlans", json={
|
|
374
|
+
"name": "US-Outbound",
|
|
375
|
+
"routeId": trunk_id,
|
|
376
|
+
"routeType": "TRUNK",
|
|
377
|
+
"dialPatterns": ["+1!"]
|
|
378
|
+
})
|
|
379
|
+
# result["id"] -> new dial plan ID
|
|
380
|
+
|
|
381
|
+
# Get dial plan details
|
|
382
|
+
dp = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}")
|
|
383
|
+
|
|
384
|
+
# Update dial plan
|
|
385
|
+
api.session.rest_put(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}", json={
|
|
386
|
+
"name": "US-Outbound-v2",
|
|
387
|
+
"routeId": trunk_id,
|
|
388
|
+
"routeType": "TRUNK"
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
# Delete dial plan
|
|
392
|
+
api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}")
|
|
393
|
+
|
|
394
|
+
# List dial patterns for a dial plan
|
|
395
|
+
patterns = api.session.rest_get(
|
|
396
|
+
f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns",
|
|
397
|
+
params={"max": 1000})
|
|
398
|
+
|
|
399
|
+
# Modify dial patterns (add/delete)
|
|
400
|
+
api.session.rest_put(
|
|
401
|
+
f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns", json={
|
|
402
|
+
"dialPatterns": [
|
|
403
|
+
{"dialPattern": "+44!", "action": "ADD"},
|
|
404
|
+
{"dialPattern": "+1!", "action": "DELETE"}
|
|
405
|
+
]
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
# Delete all dial patterns
|
|
409
|
+
api.session.rest_put(
|
|
410
|
+
f"{BASE}/telephony/config/premisePstn/dialPlans/{dial_plan_id}/dialPatterns", json={
|
|
411
|
+
"deleteAllDialPatterns": True
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
# Validate dial patterns
|
|
415
|
+
result = api.session.rest_post(
|
|
416
|
+
f"{BASE}/telephony/config/premisePstn/actions/validateDialPatterns/invoke", json={
|
|
417
|
+
"dialPatterns": ["+1408!", "+44!", "9XXX"]
|
|
418
|
+
})
|
|
419
|
+
# result["status"] -> "OK" or "ERRORS"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**URL summary:**
|
|
423
|
+
|
|
424
|
+
| Operation | Method | URL |
|
|
425
|
+
|-----------|--------|-----|
|
|
426
|
+
| List | GET | `{BASE}/telephony/config/premisePstn/dialPlans` |
|
|
427
|
+
| Create | POST | `{BASE}/telephony/config/premisePstn/dialPlans` |
|
|
428
|
+
| Get | GET | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
|
|
429
|
+
| Update | PUT | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
|
|
430
|
+
| Delete | DELETE | `{BASE}/telephony/config/premisePstn/dialPlans/{id}` |
|
|
431
|
+
| List patterns | GET | `{BASE}/telephony/config/premisePstn/dialPlans/{id}/dialPatterns` |
|
|
432
|
+
| Modify patterns | PUT | `{BASE}/telephony/config/premisePstn/dialPlans/{id}/dialPatterns` |
|
|
433
|
+
| Validate patterns | POST | `{BASE}/telephony/config/premisePstn/actions/validateDialPatterns/invoke` |
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Trunks
|
|
438
|
+
|
|
439
|
+
A Trunk is a SIP connection between Webex Calling and on-premises infrastructure (local gateway or SBC). Trunks can be assigned to route groups for failover/load distribution.
|
|
440
|
+
|
|
441
|
+
### Data Models
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
class TrunkType(str, Enum):
|
|
445
|
+
registering = 'REGISTERING' # Cisco CUBE Local Gateway
|
|
446
|
+
certificate_base = 'CERTIFICATE_BASED' # Cisco UBE, Oracle ACME SBC,
|
|
447
|
+
# AudioCodes SBC, Ribbon SBC
|
|
448
|
+
|
|
449
|
+
class Trunk(ApiModel):
|
|
450
|
+
trunk_id: str # alias 'id'
|
|
451
|
+
name: str
|
|
452
|
+
location: IdAndName
|
|
453
|
+
in_use: bool
|
|
454
|
+
trunk_type: TrunkType
|
|
455
|
+
is_restricted_to_dedicated_instance: bool
|
|
456
|
+
|
|
457
|
+
class TrunkDetail(ApiModel):
|
|
458
|
+
trunk_id: str # alias 'id'
|
|
459
|
+
name: str
|
|
460
|
+
organization: Customer
|
|
461
|
+
location: IdAndName
|
|
462
|
+
otg_dtg_id: str # outgoing/destination trunk group ID
|
|
463
|
+
line_port: str # device endpoint / SIP URI
|
|
464
|
+
locations_using_trunk: list[IdAndName]
|
|
465
|
+
pilot_user_id: str
|
|
466
|
+
outbound_proxy: Any
|
|
467
|
+
sip_authentication_user_name: str
|
|
468
|
+
status: DeviceStatus # 'online' | 'offline' | 'unknown'
|
|
469
|
+
error_codes: list[str]
|
|
470
|
+
response_status: list[ResponseStatus]
|
|
471
|
+
dual_identity_support_enabled: bool
|
|
472
|
+
trunk_type: TrunkType
|
|
473
|
+
device_type: str
|
|
474
|
+
address: Optional[str] # FQDN/SRV -- certificate-based only
|
|
475
|
+
domain: Optional[str] # certificate-based only
|
|
476
|
+
port: Optional[int] # certificate-based only
|
|
477
|
+
max_concurrent_calls: int
|
|
478
|
+
is_restricted_to_dedicated_instance: Optional[bool]
|
|
479
|
+
p_charge_info_support_policy: Optional[PChargeInfoSupportPolicy]
|
|
480
|
+
|
|
481
|
+
class TrunkDeviceType(ApiModel):
|
|
482
|
+
device_type: str
|
|
483
|
+
min_concurrent_calls: int
|
|
484
|
+
max_concurrent_calls: int
|
|
485
|
+
|
|
486
|
+
class TrunkTypeWithDeviceType(ApiModel):
|
|
487
|
+
trunk_type: TrunkType
|
|
488
|
+
device_types: list[TrunkDeviceType]
|
|
489
|
+
|
|
490
|
+
class DeviceStatus(str, Enum):
|
|
491
|
+
online = 'online'
|
|
492
|
+
offline = 'offline'
|
|
493
|
+
unknown = 'unknown'
|
|
494
|
+
|
|
495
|
+
class PChargeInfoSupportPolicy(str, Enum):
|
|
496
|
+
disabled = 'DISABLED'
|
|
497
|
+
asserted_identity = 'ASSERTED_IDENTITY'
|
|
498
|
+
configurable_charge_number = 'CONFIGURABLE_CHARGE_NUMBER'
|
|
499
|
+
|
|
500
|
+
class TrunkUsage(ApiModel):
|
|
501
|
+
pstn_connection_count: int
|
|
502
|
+
call_to_extension_count: int
|
|
503
|
+
dial_plan_count: int
|
|
504
|
+
route_group_count: int
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Trunk Types
|
|
508
|
+
|
|
509
|
+
| Type | Value | Use Case |
|
|
510
|
+
|------|-------|----------|
|
|
511
|
+
| Registering | `REGISTERING` | Cisco CUBE Local Gateway. Registers with Webex Calling cloud. Requires password. |
|
|
512
|
+
| Certificate-based | `CERTIFICATE_BASED` | Cisco UBE, Oracle ACME SBC, AudioCodes SBC, Ribbon SBC. Uses mutual TLS. Requires FQDN/SRV address, domain, port, and max concurrent calls. |
|
|
513
|
+
|
|
514
|
+
### Dual Identity Support
|
|
515
|
+
|
|
516
|
+
The `dual_identity_support_enabled` setting controls the From and P-Asserted-Identity (PAI) headers on outbound SIP INVITEs sent to the trunk. When enabled, the From header may differ from the PAI, allowing the called party to see the user's identity while the trunk authenticates with a different identity.
|
|
517
|
+
|
|
518
|
+
### P-Charge-Info Support Policy
|
|
519
|
+
|
|
520
|
+
Controls the P-Charge-Info header on outbound PSTN calls:
|
|
521
|
+
|
|
522
|
+
- **DISABLED**: No P-Charge-Info header sent.
|
|
523
|
+
- **ASSERTED_IDENTITY**: Always uses the Webex Calling primary number or the location's main number.
|
|
524
|
+
- **CONFIGURABLE_CHARGE_NUMBER**: Uses the originating entity's location charge number if set, else the entity's primary number (non-toll-free), else the location main number (non-toll-free), else falls back to ASSERTED_IDENTITY behavior.
|
|
525
|
+
|
|
526
|
+
### API Methods
|
|
527
|
+
|
|
528
|
+
#### List Trunks
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
TrunkApi.list(
|
|
532
|
+
name: str = None,
|
|
533
|
+
location_name: str = None,
|
|
534
|
+
trunk_type: str = None,
|
|
535
|
+
order: str = None, # sort fields: name, locationName
|
|
536
|
+
org_id: str = None
|
|
537
|
+
) -> Generator[Trunk, None, None]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### Create Trunk
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
TrunkApi.create(
|
|
544
|
+
name: str,
|
|
545
|
+
location_id: str,
|
|
546
|
+
password: str,
|
|
547
|
+
trunk_type: TrunkType = TrunkType.registering,
|
|
548
|
+
dual_identity_support_enabled: bool = None,
|
|
549
|
+
device_type: TrunkDeviceType = None,
|
|
550
|
+
address: str = None, # FQDN/SRV -- required for certificate-based
|
|
551
|
+
domain: str = None, # required for certificate-based
|
|
552
|
+
port: int = None, # required for certificate-based
|
|
553
|
+
max_concurrent_calls: int = None, # required for certificate-based
|
|
554
|
+
p_charge_info_support_policy: PChargeInfoSupportPolicy = None,
|
|
555
|
+
org_id: str = None
|
|
556
|
+
) -> str # returns new trunk ID
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### Get Trunk Details
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
TrunkApi.details(
|
|
563
|
+
trunk_id: str,
|
|
564
|
+
org_id: str = None
|
|
565
|
+
) -> TrunkDetail
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### Update Trunk
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
TrunkApi.update(
|
|
572
|
+
trunk_id: str,
|
|
573
|
+
name: str,
|
|
574
|
+
password: str,
|
|
575
|
+
dual_identity_support_enabled: bool = None,
|
|
576
|
+
max_concurrent_calls: int = None,
|
|
577
|
+
p_charge_info_support_policy: PChargeInfoSupportPolicy = None,
|
|
578
|
+
org_id: str = None
|
|
579
|
+
) -> None
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Note: `name` and `password` are always required for updates.
|
|
583
|
+
|
|
584
|
+
**Limitation**: You cannot change `trunk_type`, `location_id`, or `device_type` after creation. To change these properties, you must delete and recreate the trunk.
|
|
585
|
+
|
|
586
|
+
#### Delete Trunk
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
TrunkApi.delete_trunk(
|
|
590
|
+
trunk_id: str,
|
|
591
|
+
org_id: str = None
|
|
592
|
+
) -> None
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### List Trunk Types with Device Types
|
|
596
|
+
|
|
597
|
+
```python
|
|
598
|
+
TrunkApi.trunk_types(
|
|
599
|
+
org_id: str = None
|
|
600
|
+
) -> List[TrunkTypeWithDeviceType]
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Returns the available trunk types and their supported device types (with min/max concurrent call limits).
|
|
604
|
+
|
|
605
|
+
#### Validate FQDN and Domain
|
|
606
|
+
|
|
607
|
+
```python
|
|
608
|
+
TrunkApi.validate_fqdn_and_domain(
|
|
609
|
+
address: str,
|
|
610
|
+
domain: str,
|
|
611
|
+
port: int = None,
|
|
612
|
+
org_id: str = None
|
|
613
|
+
) -> None
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Validates the FQDN/SRV address and domain before creating a certificate-based trunk. Raises an exception on validation failure.
|
|
617
|
+
|
|
618
|
+
#### Get Trunk Usage
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
TrunkApi.usage(
|
|
622
|
+
trunk_id: str,
|
|
623
|
+
org_id: str = None
|
|
624
|
+
) -> TrunkUsage
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Returns counts of PSTN connections, call-to-extension locations, dial plans, and route groups using this trunk.
|
|
628
|
+
|
|
629
|
+
#### Trunk Usage Detail Methods
|
|
630
|
+
|
|
631
|
+
```python
|
|
632
|
+
TrunkApi.usage_call_to_extension(trunk_id, order=None, name=None, org_id=None) -> Generator[IdAndName]
|
|
633
|
+
TrunkApi.usage_dial_plan(trunk_id, order=None, name=None, org_id=None) -> Generator[IdAndName]
|
|
634
|
+
TrunkApi.usage_location_pstn(trunk_id, org_id=None) -> Generator[IdAndName]
|
|
635
|
+
TrunkApi.usage_route_group(trunk_id, org_id=None) -> Generator[IdAndName]
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Usage Example
|
|
639
|
+
|
|
640
|
+
```python
|
|
641
|
+
from wxc_sdk.telephony.prem_pstn.trunk import TrunkType
|
|
642
|
+
|
|
643
|
+
# Create a registering trunk
|
|
644
|
+
trunk_id = api.telephony.prem_pstn.trunk.create(
|
|
645
|
+
name='HQ-LGW-01',
|
|
646
|
+
location_id=location_id,
|
|
647
|
+
password='SecurePass123!',
|
|
648
|
+
trunk_type=TrunkType.registering
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# Check trunk status
|
|
652
|
+
detail = api.telephony.prem_pstn.trunk.details(trunk_id=trunk_id)
|
|
653
|
+
print(f"Status: {detail.status}") # online / offline / unknown
|
|
654
|
+
print(f"SIP User: {detail.sip_authentication_user_name}")
|
|
655
|
+
print(f"Line/Port: {detail.line_port}")
|
|
656
|
+
print(f"OTG/DTG: {detail.otg_dtg_id}")
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### CLI Examples
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
# List all trunks
|
|
663
|
+
wxcli call-routing list-trunks
|
|
664
|
+
|
|
665
|
+
# Filter trunks by name
|
|
666
|
+
wxcli call-routing list-trunks --name "HQ-LGW"
|
|
667
|
+
|
|
668
|
+
# Filter by location
|
|
669
|
+
wxcli call-routing list-trunks --location-name "Raleigh"
|
|
670
|
+
|
|
671
|
+
# Filter by trunk type
|
|
672
|
+
wxcli call-routing list-trunks --trunk-type REGISTERING
|
|
673
|
+
|
|
674
|
+
# Get trunk details
|
|
675
|
+
wxcli call-routing show-trunks Y2lzY29zcGFyazovL_TRUNK_ID
|
|
676
|
+
|
|
677
|
+
# List available trunk types and their device types
|
|
678
|
+
wxcli call-routing list-trunk-types
|
|
679
|
+
|
|
680
|
+
# Create a registering trunk (Cisco CUBE Local Gateway)
|
|
681
|
+
wxcli call-routing create-trunks --name "HQ-LGW-01" \
|
|
682
|
+
--location-id Y2lzY29zcGFyazovL_LOC_ID \
|
|
683
|
+
--password "SecurePass123!"
|
|
684
|
+
|
|
685
|
+
# Create a certificate-based trunk with FQDN
|
|
686
|
+
wxcli call-routing create-trunks --name "HQ-SBC-01" \
|
|
687
|
+
--location-id Y2lzY29zcGFyazovL_LOC_ID \
|
|
688
|
+
--password "SecurePass123!" \
|
|
689
|
+
--address "sbc.example.com" \
|
|
690
|
+
--domain "example.com" \
|
|
691
|
+
--port 5061 \
|
|
692
|
+
--max-concurrent-calls 100
|
|
693
|
+
|
|
694
|
+
# Create with full JSON body (includes device type and dual identity)
|
|
695
|
+
wxcli call-routing create-trunks --json-body '{
|
|
696
|
+
"name": "HQ-LGW-02",
|
|
697
|
+
"locationId": "Y2lzY29zcGFyazovL_LOC_ID",
|
|
698
|
+
"password": "SecurePass123!",
|
|
699
|
+
"trunkType": "REGISTERING",
|
|
700
|
+
"dualIdentitySupportEnabled": true,
|
|
701
|
+
"deviceType": "Cisco Unified Border Element"
|
|
702
|
+
}'
|
|
703
|
+
|
|
704
|
+
# Update a trunk (rename and change password)
|
|
705
|
+
wxcli call-routing update-trunks Y2lzY29zcGFyazovL_TRUNK_ID \
|
|
706
|
+
--name "HQ-LGW-01-v2" --password "NewPass456!"
|
|
707
|
+
|
|
708
|
+
# Validate FQDN and domain before creating a certificate-based trunk
|
|
709
|
+
wxcli call-routing validate-local-gateway \
|
|
710
|
+
--address "sbc.example.com" --domain "example.com" --port 5061
|
|
711
|
+
|
|
712
|
+
# Get trunk usage count (how many dial plans, route groups, etc. use it)
|
|
713
|
+
wxcli call-routing show Y2lzY29zcGFyazovL_TRUNK_ID
|
|
714
|
+
|
|
715
|
+
# List dial plans using this trunk
|
|
716
|
+
wxcli call-routing list Y2lzY29zcGFyazovL_TRUNK_ID
|
|
717
|
+
|
|
718
|
+
# List route groups using this trunk
|
|
719
|
+
wxcli call-routing list-usage-route-group Y2lzY29zcGFyazovL_TRUNK_ID
|
|
720
|
+
|
|
721
|
+
# List locations using this trunk as PSTN connection
|
|
722
|
+
wxcli call-routing list-usage-pstn-connection-trunks Y2lzY29zcGFyazovL_TRUNK_ID
|
|
723
|
+
|
|
724
|
+
# List call-to-extension locations for this trunk
|
|
725
|
+
wxcli call-routing list-usage-call-to-extension-trunks Y2lzY29zcGFyazovL_TRUNK_ID
|
|
726
|
+
|
|
727
|
+
# Delete a trunk
|
|
728
|
+
wxcli call-routing delete-trunks Y2lzY29zcGFyazovL_TRUNK_ID --force
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### Raw HTTP
|
|
732
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
733
|
+
|
|
734
|
+
All trunk endpoints live under the `/premisePstn/trunks` prefix.
|
|
735
|
+
|
|
736
|
+
```python
|
|
737
|
+
BASE = "https://webexapis.com/v1"
|
|
738
|
+
|
|
739
|
+
# List trunks
|
|
740
|
+
trunks = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks",
|
|
741
|
+
params={"max": 1000})
|
|
742
|
+
# trunks["trunks"] -> list of dicts
|
|
743
|
+
|
|
744
|
+
# Create trunk (registering type)
|
|
745
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/trunks", json={
|
|
746
|
+
"name": "HQ-LGW-01",
|
|
747
|
+
"locationId": location_id,
|
|
748
|
+
"password": "SecurePass123!",
|
|
749
|
+
"trunkType": "REGISTERING"
|
|
750
|
+
})
|
|
751
|
+
# result["id"] -> new trunk ID
|
|
752
|
+
|
|
753
|
+
# Create trunk (certificate-based)
|
|
754
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/trunks", json={
|
|
755
|
+
"name": "HQ-SBC-01",
|
|
756
|
+
"locationId": location_id,
|
|
757
|
+
"password": "SecurePass123!",
|
|
758
|
+
"trunkType": "CERTIFICATE_BASED",
|
|
759
|
+
"address": "sbc.example.com",
|
|
760
|
+
"domain": "example.com",
|
|
761
|
+
"port": 5061,
|
|
762
|
+
"maxConcurrentCalls": 100
|
|
763
|
+
})
|
|
764
|
+
|
|
765
|
+
# Get trunk details
|
|
766
|
+
detail = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}")
|
|
767
|
+
|
|
768
|
+
# Update trunk (name + password always required)
|
|
769
|
+
api.session.rest_put(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}", json={
|
|
770
|
+
"name": "HQ-LGW-01-v2",
|
|
771
|
+
"password": "NewPass456!"
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
# Delete trunk
|
|
775
|
+
api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}")
|
|
776
|
+
|
|
777
|
+
# List trunk types with device types
|
|
778
|
+
types = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/trunkTypes")
|
|
779
|
+
# types["trunkTypes"] -> list of dicts
|
|
780
|
+
|
|
781
|
+
# Validate FQDN and domain (certificate-based)
|
|
782
|
+
api.session.rest_post(
|
|
783
|
+
f"{BASE}/telephony/config/premisePstn/trunks/actions/fqdnValidation/invoke", json={
|
|
784
|
+
"address": "sbc.example.com",
|
|
785
|
+
"domain": "example.com",
|
|
786
|
+
"port": 5061
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
# Trunk usage count
|
|
790
|
+
usage = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usage")
|
|
791
|
+
|
|
792
|
+
# Trunk usage details
|
|
793
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageDialPlan")
|
|
794
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usagePstnConnection")
|
|
795
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageRouteGroup")
|
|
796
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/trunks/{trunk_id}/usageCallToExtension")
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**URL summary:**
|
|
800
|
+
|
|
801
|
+
| Operation | Method | URL |
|
|
802
|
+
|-----------|--------|-----|
|
|
803
|
+
| List | GET | `{BASE}/telephony/config/premisePstn/trunks` |
|
|
804
|
+
| Create | POST | `{BASE}/telephony/config/premisePstn/trunks` |
|
|
805
|
+
| Get | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
|
|
806
|
+
| Update | PUT | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
|
|
807
|
+
| Delete | DELETE | `{BASE}/telephony/config/premisePstn/trunks/{id}` |
|
|
808
|
+
| List types | GET | `{BASE}/telephony/config/premisePstn/trunks/trunkTypes` |
|
|
809
|
+
| Validate FQDN | POST | `{BASE}/telephony/config/premisePstn/trunks/actions/fqdnValidation/invoke` |
|
|
810
|
+
| Usage count | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usage` |
|
|
811
|
+
| Usage dial plan | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageDialPlan` |
|
|
812
|
+
| Usage PSTN | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usagePstnConnection` |
|
|
813
|
+
| Usage route group | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageRouteGroup` |
|
|
814
|
+
| Usage call-to-ext | GET | `{BASE}/telephony/config/premisePstn/trunks/{id}/usageCallToExtension` |
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Route Groups
|
|
819
|
+
|
|
820
|
+
A Route Group is a collection of trunks (up to 10, from different locations) that enables failover and load distribution for on-premises call routing.
|
|
821
|
+
|
|
822
|
+
### Data Models
|
|
823
|
+
|
|
824
|
+
```python
|
|
825
|
+
class RGTrunk(ApiModel):
|
|
826
|
+
trunk_id: str # alias 'id'
|
|
827
|
+
name: Optional[str]
|
|
828
|
+
location_id: Optional[str]
|
|
829
|
+
priority: int
|
|
830
|
+
|
|
831
|
+
class RouteGroup(ApiModel):
|
|
832
|
+
rg_id: Optional[str] # alias 'id' -- only in list(), not detail()
|
|
833
|
+
name: str
|
|
834
|
+
in_use: Optional[bool] # only in list()
|
|
835
|
+
organization: Optional[Customer] # only in detail()
|
|
836
|
+
local_gateways: Optional[list[RGTrunk]] # only in detail()
|
|
837
|
+
|
|
838
|
+
class RouteGroupUsage(ApiModel):
|
|
839
|
+
pstn_connection_count: int
|
|
840
|
+
call_to_extension_count: int
|
|
841
|
+
dial_plan_count: int
|
|
842
|
+
route_list_count: int
|
|
843
|
+
|
|
844
|
+
class UsageRouteLists(ApiModel):
|
|
845
|
+
rl_id: str # alias 'id'
|
|
846
|
+
rl_name: str # alias 'name'
|
|
847
|
+
location_id: str
|
|
848
|
+
location_name: str
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### API Methods
|
|
852
|
+
|
|
853
|
+
#### List Route Groups
|
|
854
|
+
|
|
855
|
+
```python
|
|
856
|
+
RouteGroupApi.list(
|
|
857
|
+
name: str = None,
|
|
858
|
+
order: str = None, # sort orders: asc, desc
|
|
859
|
+
org_id: str = None
|
|
860
|
+
) -> Generator[RouteGroup, None, None]
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
#### Create Route Group
|
|
864
|
+
|
|
865
|
+
```python
|
|
866
|
+
RouteGroupApi.create(
|
|
867
|
+
route_group: RouteGroup,
|
|
868
|
+
org_id: str = None
|
|
869
|
+
) -> str # returns new route group ID
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
The `RouteGroup` object must have `name` and `local_gateways` set. Each `RGTrunk` in `local_gateways` must have `trunk_id` and `priority` set.
|
|
873
|
+
|
|
874
|
+
#### Get Route Group Details
|
|
875
|
+
|
|
876
|
+
```python
|
|
877
|
+
RouteGroupApi.details(
|
|
878
|
+
rg_id: str,
|
|
879
|
+
org_id: str = None
|
|
880
|
+
) -> RouteGroup
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
Returns the route group with `organization` and `local_gateways` populated.
|
|
884
|
+
|
|
885
|
+
#### Update Route Group
|
|
886
|
+
|
|
887
|
+
```python
|
|
888
|
+
RouteGroupApi.update(
|
|
889
|
+
rg_id: str,
|
|
890
|
+
update: RouteGroup,
|
|
891
|
+
org_id: str = None
|
|
892
|
+
) -> None
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
#### Delete Route Group
|
|
896
|
+
|
|
897
|
+
```python
|
|
898
|
+
RouteGroupApi.delete_route_group(
|
|
899
|
+
rg_id: str,
|
|
900
|
+
org_id: str = None
|
|
901
|
+
) -> None
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
#### Get Route Group Usage
|
|
905
|
+
|
|
906
|
+
```python
|
|
907
|
+
RouteGroupApi.usage(
|
|
908
|
+
rg_id: str,
|
|
909
|
+
org_id: str = None
|
|
910
|
+
) -> RouteGroupUsage
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
#### Route Group Usage Detail Methods
|
|
914
|
+
|
|
915
|
+
```python
|
|
916
|
+
RouteGroupApi.usage_call_to_extension(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
|
|
917
|
+
RouteGroupApi.usage_dial_plan(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
|
|
918
|
+
RouteGroupApi.usage_location_pstn(rg_id, location_name=None, order=None, org_id=None) -> Generator[IdAndName]
|
|
919
|
+
RouteGroupApi.usage_route_lists(rg_id, name=None, order=None, org_id=None) -> Generator[UsageRouteLists]
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
### Usage Example
|
|
923
|
+
|
|
924
|
+
```python
|
|
925
|
+
from wxc_sdk.telephony.prem_pstn.route_group import RouteGroup, RGTrunk
|
|
926
|
+
|
|
927
|
+
# Create a route group with two trunks (priority-based failover)
|
|
928
|
+
rg = RouteGroup(
|
|
929
|
+
name='US-East-RG',
|
|
930
|
+
local_gateways=[
|
|
931
|
+
RGTrunk(trunk_id=primary_trunk_id, priority=1),
|
|
932
|
+
RGTrunk(trunk_id=backup_trunk_id, priority=2)
|
|
933
|
+
]
|
|
934
|
+
)
|
|
935
|
+
rg_id = api.telephony.prem_pstn.route_group.create(route_group=rg)
|
|
936
|
+
|
|
937
|
+
# Check what's using this route group
|
|
938
|
+
usage = api.telephony.prem_pstn.route_group.usage(rg_id=rg_id)
|
|
939
|
+
print(f"Dial plans: {usage.dial_plan_count}, Route lists: {usage.route_list_count}")
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
### CLI Examples
|
|
943
|
+
|
|
944
|
+
```bash
|
|
945
|
+
# List all route groups
|
|
946
|
+
wxcli call-routing list-route-groups
|
|
947
|
+
|
|
948
|
+
# Filter by name
|
|
949
|
+
wxcli call-routing list-route-groups --name "US-East"
|
|
950
|
+
|
|
951
|
+
# Get route group details (includes trunk assignments)
|
|
952
|
+
wxcli call-routing show-route-groups Y2lzY29zcGFyazovL_RG_ID
|
|
953
|
+
|
|
954
|
+
# Create a route group with trunk assignments (requires --json-body for localGateways array)
|
|
955
|
+
wxcli call-routing create-route-groups --json-body '{
|
|
956
|
+
"name": "US-East-RG",
|
|
957
|
+
"localGateways": [
|
|
958
|
+
{"id": "Y2lzY29zcGFyazovL_PRIMARY_TRUNK_ID", "priority": "1"},
|
|
959
|
+
{"id": "Y2lzY29zcGFyazovL_BACKUP_TRUNK_ID", "priority": "2"}
|
|
960
|
+
]
|
|
961
|
+
}'
|
|
962
|
+
|
|
963
|
+
# Create with just a name (trunks can be added later via update)
|
|
964
|
+
wxcli call-routing create-route-groups --name "US-East-RG"
|
|
965
|
+
|
|
966
|
+
# Update a route group (rename or change trunk assignments)
|
|
967
|
+
wxcli call-routing update-route-groups Y2lzY29zcGFyazovL_RG_ID \
|
|
968
|
+
--name "US-East-RG-v2"
|
|
969
|
+
|
|
970
|
+
# Update trunk assignments via --json-body
|
|
971
|
+
wxcli call-routing update-route-groups Y2lzY29zcGFyazovL_RG_ID --json-body '{
|
|
972
|
+
"name": "US-East-RG-v2",
|
|
973
|
+
"localGateways": [
|
|
974
|
+
{"id": "Y2lzY29zcGFyazovL_PRIMARY_TRUNK_ID", "priority": "1"}
|
|
975
|
+
]
|
|
976
|
+
}'
|
|
977
|
+
|
|
978
|
+
# Get route group usage count
|
|
979
|
+
wxcli call-routing show-usage Y2lzY29zcGFyazovL_RG_ID
|
|
980
|
+
|
|
981
|
+
# List dial plan locations using this route group
|
|
982
|
+
wxcli call-routing list-usage-dial-plan Y2lzY29zcGFyazovL_RG_ID
|
|
983
|
+
|
|
984
|
+
# List PSTN connection locations using this route group
|
|
985
|
+
wxcli call-routing list-usage-pstn-connection-route-groups Y2lzY29zcGFyazovL_RG_ID
|
|
986
|
+
|
|
987
|
+
# List route lists using this route group
|
|
988
|
+
wxcli call-routing list-usage-route-list Y2lzY29zcGFyazovL_RG_ID
|
|
989
|
+
|
|
990
|
+
# List call-to-extension locations using this route group
|
|
991
|
+
wxcli call-routing list-usage-call-to-extension-route-groups Y2lzY29zcGFyazovL_RG_ID
|
|
992
|
+
|
|
993
|
+
# Delete a route group
|
|
994
|
+
wxcli call-routing delete-route-groups Y2lzY29zcGFyazovL_RG_ID --force
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
### Raw HTTP
|
|
998
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
999
|
+
|
|
1000
|
+
All route group endpoints live under the `/premisePstn/routeGroups` prefix.
|
|
1001
|
+
|
|
1002
|
+
```python
|
|
1003
|
+
BASE = "https://webexapis.com/v1"
|
|
1004
|
+
|
|
1005
|
+
# List route groups
|
|
1006
|
+
groups = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups",
|
|
1007
|
+
params={"max": 1000})
|
|
1008
|
+
# groups["routeGroups"] -> list of dicts
|
|
1009
|
+
|
|
1010
|
+
# Create route group
|
|
1011
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/routeGroups", json={
|
|
1012
|
+
"name": "US-East-RG",
|
|
1013
|
+
"localGateways": [
|
|
1014
|
+
{"trunkId": primary_trunk_id, "priority": 1},
|
|
1015
|
+
{"trunkId": backup_trunk_id, "priority": 2}
|
|
1016
|
+
]
|
|
1017
|
+
})
|
|
1018
|
+
# result["id"] -> new route group ID
|
|
1019
|
+
|
|
1020
|
+
# Get route group details
|
|
1021
|
+
rg = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}")
|
|
1022
|
+
|
|
1023
|
+
# Update route group
|
|
1024
|
+
api.session.rest_put(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}", json={
|
|
1025
|
+
"name": "US-East-RG-v2",
|
|
1026
|
+
"localGateways": [
|
|
1027
|
+
{"trunkId": primary_trunk_id, "priority": 1}
|
|
1028
|
+
]
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
# Delete route group
|
|
1032
|
+
api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}")
|
|
1033
|
+
|
|
1034
|
+
# Route group usage count
|
|
1035
|
+
usage = api.session.rest_get(
|
|
1036
|
+
f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usage")
|
|
1037
|
+
|
|
1038
|
+
# Route group usage details
|
|
1039
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageCallToExtension")
|
|
1040
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageDialPlan")
|
|
1041
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usagePstnConnection")
|
|
1042
|
+
api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeGroups/{rg_id}/usageRouteList")
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
**URL summary:**
|
|
1046
|
+
|
|
1047
|
+
| Operation | Method | URL |
|
|
1048
|
+
|-----------|--------|-----|
|
|
1049
|
+
| List | GET | `{BASE}/telephony/config/premisePstn/routeGroups` |
|
|
1050
|
+
| Create | POST | `{BASE}/telephony/config/premisePstn/routeGroups` |
|
|
1051
|
+
| Get | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
|
|
1052
|
+
| Update | PUT | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
|
|
1053
|
+
| Delete | DELETE | `{BASE}/telephony/config/premisePstn/routeGroups/{id}` |
|
|
1054
|
+
| Usage count | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usage` |
|
|
1055
|
+
| Usage call-to-ext | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageCallToExtension` |
|
|
1056
|
+
| Usage dial plan | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageDialPlan` |
|
|
1057
|
+
| Usage PSTN | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usagePstnConnection` |
|
|
1058
|
+
| Usage route list | GET | `{BASE}/telephony/config/premisePstn/routeGroups/{id}/usageRouteList` |
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
1062
|
+
## Route Lists
|
|
1063
|
+
|
|
1064
|
+
A Route List is a list of phone numbers that can be reached via a Route Group. Route lists are used to provide cloud PSTN connectivity to Webex Calling Dedicated Instance.
|
|
1065
|
+
|
|
1066
|
+
### Data Models
|
|
1067
|
+
|
|
1068
|
+
```python
|
|
1069
|
+
class RouteListDetail(ApiModel):
|
|
1070
|
+
rl_id: str # alias 'id'
|
|
1071
|
+
name: str
|
|
1072
|
+
location: IdAndName
|
|
1073
|
+
route_group: IdAndName
|
|
1074
|
+
|
|
1075
|
+
class RouteList(ApiModel):
|
|
1076
|
+
rl_id: str # alias 'id'
|
|
1077
|
+
name: str
|
|
1078
|
+
location_id: str
|
|
1079
|
+
location_name: str
|
|
1080
|
+
rg_id: str # alias 'routeGroupId'
|
|
1081
|
+
rg_name: str # alias 'routeGroupName'
|
|
1082
|
+
peak_active_route_list_calls_org: Optional[int]
|
|
1083
|
+
current_active_route_list_calls_org: Optional[int]
|
|
1084
|
+
route_list_calls_volume_org: Optional[int]
|
|
1085
|
+
peak_active_route_list_calls: Optional[int]
|
|
1086
|
+
current_active_route_list_calls: Optional[int]
|
|
1087
|
+
|
|
1088
|
+
class NumberAndAction(ApiModel):
|
|
1089
|
+
number: str
|
|
1090
|
+
action: PatternAction # 'ADD' | 'DELETE'
|
|
1091
|
+
# helper statics: NumberAndAction.add(number), NumberAndAction.delete(number)
|
|
1092
|
+
|
|
1093
|
+
class UpdateNumbersResponse(ApiModel):
|
|
1094
|
+
number: str
|
|
1095
|
+
number_status: str
|
|
1096
|
+
message: str
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
### API Methods
|
|
1100
|
+
|
|
1101
|
+
#### List Route Lists
|
|
1102
|
+
|
|
1103
|
+
```python
|
|
1104
|
+
RouteListApi.list(
|
|
1105
|
+
name: list[str] = None,
|
|
1106
|
+
location_id: list[str] = None,
|
|
1107
|
+
order: str = None, # sort fields: name, locationId
|
|
1108
|
+
org_id: str = None
|
|
1109
|
+
) -> Generator[RouteList, None, None]
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
Note: `name` and `location_id` accept lists for multi-value filtering.
|
|
1113
|
+
|
|
1114
|
+
#### Create Route List
|
|
1115
|
+
|
|
1116
|
+
```python
|
|
1117
|
+
RouteListApi.create(
|
|
1118
|
+
name: str,
|
|
1119
|
+
location_id: str,
|
|
1120
|
+
rg_id: str, # route group ID
|
|
1121
|
+
org_id: str = None
|
|
1122
|
+
) -> str # returns new route list ID
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
#### Get Route List Details
|
|
1126
|
+
|
|
1127
|
+
```python
|
|
1128
|
+
RouteListApi.details(
|
|
1129
|
+
rl_id: str,
|
|
1130
|
+
org_id: str = None
|
|
1131
|
+
) -> RouteListDetail
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
#### Update Route List
|
|
1135
|
+
|
|
1136
|
+
```python
|
|
1137
|
+
RouteListApi.update(
|
|
1138
|
+
rl_id: str,
|
|
1139
|
+
name: str = None,
|
|
1140
|
+
rg_id: str = None,
|
|
1141
|
+
org_id: str = None
|
|
1142
|
+
) -> None
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
#### Delete Route List
|
|
1146
|
+
|
|
1147
|
+
```python
|
|
1148
|
+
RouteListApi.delete_route_list(
|
|
1149
|
+
rl_id: str,
|
|
1150
|
+
org_id: str = None
|
|
1151
|
+
) -> None
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
#### List Numbers on a Route List
|
|
1155
|
+
|
|
1156
|
+
```python
|
|
1157
|
+
RouteListApi.numbers(
|
|
1158
|
+
rl_id: str,
|
|
1159
|
+
order: str = None,
|
|
1160
|
+
number: str = None,
|
|
1161
|
+
org_id: str = None
|
|
1162
|
+
) -> Generator[str, None, None]
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
#### Modify Numbers on a Route List (Add/Delete)
|
|
1166
|
+
|
|
1167
|
+
```python
|
|
1168
|
+
RouteListApi.update_numbers(
|
|
1169
|
+
rl_id: str,
|
|
1170
|
+
numbers: List[NumberAndAction] = None,
|
|
1171
|
+
delete_all_numbers: bool = None,
|
|
1172
|
+
org_id: str = None
|
|
1173
|
+
) -> List[UpdateNumbersResponse]
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
If `delete_all_numbers` is set, the `numbers` array is ignored and all numbers are removed.
|
|
1177
|
+
|
|
1178
|
+
#### Delete All Numbers from a Route List
|
|
1179
|
+
|
|
1180
|
+
```python
|
|
1181
|
+
RouteListApi.delete_all_numbers(
|
|
1182
|
+
rl_id: str,
|
|
1183
|
+
org_id: str = None
|
|
1184
|
+
) -> None
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
### Usage Example
|
|
1188
|
+
|
|
1189
|
+
```python
|
|
1190
|
+
from wxc_sdk.telephony.prem_pstn.route_list import NumberAndAction
|
|
1191
|
+
|
|
1192
|
+
# Create route list
|
|
1193
|
+
rl_id = api.telephony.prem_pstn.route_list.create(
|
|
1194
|
+
name='US-East-Numbers',
|
|
1195
|
+
location_id=location_id,
|
|
1196
|
+
rg_id=rg_id
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
# Add numbers
|
|
1200
|
+
result = api.telephony.prem_pstn.route_list.update_numbers(
|
|
1201
|
+
rl_id=rl_id,
|
|
1202
|
+
numbers=[
|
|
1203
|
+
NumberAndAction.add('+19195551234'),
|
|
1204
|
+
NumberAndAction.add('+19195555678')
|
|
1205
|
+
]
|
|
1206
|
+
)
|
|
1207
|
+
for r in result:
|
|
1208
|
+
print(f"{r.number}: {r.number_status} - {r.message}")
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
### CLI Examples
|
|
1212
|
+
|
|
1213
|
+
```bash
|
|
1214
|
+
# List all route lists
|
|
1215
|
+
wxcli call-routing list-route-lists
|
|
1216
|
+
|
|
1217
|
+
# Filter by name
|
|
1218
|
+
wxcli call-routing list-route-lists --name "US-East-Numbers"
|
|
1219
|
+
|
|
1220
|
+
# Filter by location
|
|
1221
|
+
wxcli call-routing list-route-lists --location-id Y2lzY29zcGFyazovL_LOC_ID
|
|
1222
|
+
|
|
1223
|
+
# Get route list details
|
|
1224
|
+
wxcli call-routing show-route-lists Y2lzY29zcGFyazovL_RL_ID
|
|
1225
|
+
|
|
1226
|
+
# Create a route list
|
|
1227
|
+
wxcli call-routing create-route-lists \
|
|
1228
|
+
--name "US-East-Numbers" \
|
|
1229
|
+
--location-id Y2lzY29zcGFyazovL_LOC_ID \
|
|
1230
|
+
--route-group-id Y2lzY29zcGFyazovL_RG_ID
|
|
1231
|
+
|
|
1232
|
+
# Update a route list (rename or change route group)
|
|
1233
|
+
wxcli call-routing update-route-lists Y2lzY29zcGFyazovL_RL_ID \
|
|
1234
|
+
--name "US-East-Numbers-v2"
|
|
1235
|
+
|
|
1236
|
+
# List numbers assigned to a route list
|
|
1237
|
+
wxcli call-routing list-numbers Y2lzY29zcGFyazovL_RL_ID
|
|
1238
|
+
|
|
1239
|
+
# Add/delete numbers on a route list (requires --json-body for numbers array)
|
|
1240
|
+
wxcli call-routing update-numbers Y2lzY29zcGFyazovL_RL_ID --json-body '{
|
|
1241
|
+
"numbers": [
|
|
1242
|
+
{"number": "+19195551234", "action": "ADD"},
|
|
1243
|
+
{"number": "+19195555678", "action": "ADD"}
|
|
1244
|
+
]
|
|
1245
|
+
}'
|
|
1246
|
+
|
|
1247
|
+
# Delete all numbers from a route list
|
|
1248
|
+
wxcli call-routing update-numbers Y2lzY29zcGFyazovL_RL_ID --delete-all-numbers
|
|
1249
|
+
|
|
1250
|
+
# Delete a route list
|
|
1251
|
+
wxcli call-routing delete-route-lists Y2lzY29zcGFyazovL_RL_ID --force
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
### Raw HTTP
|
|
1255
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
1256
|
+
|
|
1257
|
+
All route list endpoints live under the `/premisePstn/routeLists` prefix.
|
|
1258
|
+
|
|
1259
|
+
```python
|
|
1260
|
+
BASE = "https://webexapis.com/v1"
|
|
1261
|
+
|
|
1262
|
+
# List route lists
|
|
1263
|
+
lists = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeLists",
|
|
1264
|
+
params={"max": 1000})
|
|
1265
|
+
# lists["routeLists"] -> list of dicts
|
|
1266
|
+
|
|
1267
|
+
# Create route list
|
|
1268
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/premisePstn/routeLists", json={
|
|
1269
|
+
"name": "US-East-Numbers",
|
|
1270
|
+
"locationId": location_id,
|
|
1271
|
+
"routeGroupId": rg_id
|
|
1272
|
+
})
|
|
1273
|
+
# result["id"] -> new route list ID
|
|
1274
|
+
|
|
1275
|
+
# Get route list details
|
|
1276
|
+
rl = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}")
|
|
1277
|
+
|
|
1278
|
+
# Update route list
|
|
1279
|
+
api.session.rest_put(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}", json={
|
|
1280
|
+
"name": "US-East-Numbers-v2",
|
|
1281
|
+
"routeGroupId": new_rg_id
|
|
1282
|
+
})
|
|
1283
|
+
|
|
1284
|
+
# Delete route list
|
|
1285
|
+
api.session.rest_delete(f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}")
|
|
1286
|
+
|
|
1287
|
+
# List numbers on route list
|
|
1288
|
+
nums = api.session.rest_get(
|
|
1289
|
+
f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers",
|
|
1290
|
+
params={"max": 1000})
|
|
1291
|
+
# nums["numbers"] -> list of dicts
|
|
1292
|
+
|
|
1293
|
+
# Modify numbers on route list (add/delete)
|
|
1294
|
+
api.session.rest_put(
|
|
1295
|
+
f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers", json={
|
|
1296
|
+
"numbers": [
|
|
1297
|
+
{"number": "+19195551234", "action": "ADD"},
|
|
1298
|
+
{"number": "+19195555678", "action": "ADD"}
|
|
1299
|
+
]
|
|
1300
|
+
})
|
|
1301
|
+
|
|
1302
|
+
# Delete all numbers from route list
|
|
1303
|
+
api.session.rest_put(
|
|
1304
|
+
f"{BASE}/telephony/config/premisePstn/routeLists/{rl_id}/numbers", json={
|
|
1305
|
+
"deleteAllNumbers": True
|
|
1306
|
+
})
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
**URL summary:**
|
|
1310
|
+
|
|
1311
|
+
| Operation | Method | URL |
|
|
1312
|
+
|-----------|--------|-----|
|
|
1313
|
+
| List | GET | `{BASE}/telephony/config/premisePstn/routeLists` |
|
|
1314
|
+
| Create | POST | `{BASE}/telephony/config/premisePstn/routeLists` |
|
|
1315
|
+
| Get | GET | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
|
|
1316
|
+
| Update | PUT | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
|
|
1317
|
+
| Delete | DELETE | `{BASE}/telephony/config/premisePstn/routeLists/{id}` |
|
|
1318
|
+
| List numbers | GET | `{BASE}/telephony/config/premisePstn/routeLists/{id}/numbers` |
|
|
1319
|
+
| Modify numbers | PUT | `{BASE}/telephony/config/premisePstn/routeLists/{id}/numbers` |
|
|
1320
|
+
|
|
1321
|
+
---
|
|
1322
|
+
|
|
1323
|
+
## Translation Patterns
|
|
1324
|
+
|
|
1325
|
+
Translation patterns manipulate dialed digits before routing a call. They apply to **outbound calls only**. Patterns can be configured at the **organization level** or the **location level**.
|
|
1326
|
+
|
|
1327
|
+
### Data Models
|
|
1328
|
+
|
|
1329
|
+
```python
|
|
1330
|
+
class TranslationPatternLevel(str, Enum):
|
|
1331
|
+
location = 'Location'
|
|
1332
|
+
organization = 'Organization'
|
|
1333
|
+
|
|
1334
|
+
class TranslationPattern(ApiModel):
|
|
1335
|
+
id: Optional[str]
|
|
1336
|
+
name: Optional[str]
|
|
1337
|
+
matching_pattern: Optional[str]
|
|
1338
|
+
replacement_pattern: Optional[str]
|
|
1339
|
+
level: Optional[TranslationPatternLevel]
|
|
1340
|
+
location: Optional[IdAndName]
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
### API Methods
|
|
1344
|
+
|
|
1345
|
+
#### List Translation Patterns
|
|
1346
|
+
|
|
1347
|
+
```python
|
|
1348
|
+
TranslationPatternsApi.list(
|
|
1349
|
+
limit_to_location_id: str = None, # filter to specific location
|
|
1350
|
+
limit_to_org_level_enabled: bool = None, # filter to org-level only
|
|
1351
|
+
order: str = None,
|
|
1352
|
+
name: str = None,
|
|
1353
|
+
matching_pattern: str = None,
|
|
1354
|
+
org_id: str = None
|
|
1355
|
+
) -> Generator[TranslationPattern, None, None]
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
#### Create Translation Pattern
|
|
1359
|
+
|
|
1360
|
+
```python
|
|
1361
|
+
TranslationPatternsApi.create(
|
|
1362
|
+
pattern: TranslationPattern,
|
|
1363
|
+
location_id: str = None, # omit for org-level, set for location-level
|
|
1364
|
+
org_id: str = None
|
|
1365
|
+
) -> str # returns new pattern ID
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
The `TranslationPattern` must have `name`, `matching_pattern`, and `replacement_pattern` set. The `id`, `level`, and `location` fields are excluded from the create payload.
|
|
1369
|
+
|
|
1370
|
+
#### Get Translation Pattern Details
|
|
1371
|
+
|
|
1372
|
+
```python
|
|
1373
|
+
TranslationPatternsApi.details(
|
|
1374
|
+
translation_id: str,
|
|
1375
|
+
location_id: str = None,
|
|
1376
|
+
org_id: str = None
|
|
1377
|
+
) -> TranslationPattern
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
#### Update Translation Pattern
|
|
1381
|
+
|
|
1382
|
+
```python
|
|
1383
|
+
TranslationPatternsApi.update(
|
|
1384
|
+
pattern: TranslationPattern, # must have .id set
|
|
1385
|
+
location_id: str = None,
|
|
1386
|
+
org_id: str = None
|
|
1387
|
+
) -> None
|
|
1388
|
+
```
|
|
1389
|
+
|
|
1390
|
+
#### Delete Translation Pattern
|
|
1391
|
+
|
|
1392
|
+
```python
|
|
1393
|
+
TranslationPatternsApi.delete(
|
|
1394
|
+
translation_id: str,
|
|
1395
|
+
location_id: str = None,
|
|
1396
|
+
org_id: str = None
|
|
1397
|
+
) -> None
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
### Endpoint Routing
|
|
1401
|
+
|
|
1402
|
+
- **Org-level**: `telephony/config/callRouting/translationPatterns`
|
|
1403
|
+
- **Location-level**: `telephony/config/locations/{location_id}/callRouting/translationPatterns`
|
|
1404
|
+
|
|
1405
|
+
### Usage Example
|
|
1406
|
+
|
|
1407
|
+
```python
|
|
1408
|
+
from wxc_sdk.telephony.call_routing.translation_pattern import TranslationPattern
|
|
1409
|
+
|
|
1410
|
+
# Create an org-level translation pattern
|
|
1411
|
+
# Rewrites 4-digit 9xxx extensions to E.164
|
|
1412
|
+
pattern = TranslationPattern(
|
|
1413
|
+
name='Ext-to-E164',
|
|
1414
|
+
matching_pattern='9XXX',
|
|
1415
|
+
replacement_pattern='+15125559000'
|
|
1416
|
+
)
|
|
1417
|
+
tp_id = api.telephony.call_routing.tp.create(pattern=pattern)
|
|
1418
|
+
|
|
1419
|
+
# Create a location-level translation pattern
|
|
1420
|
+
# NOTE: replacement_pattern must use literal digits only — X wildcards are rejected (error 28043)
|
|
1421
|
+
pattern_loc = TranslationPattern(
|
|
1422
|
+
name='Local-Rewrite',
|
|
1423
|
+
matching_pattern='+1919555XXXX',
|
|
1424
|
+
replacement_pattern='+19195550000'
|
|
1425
|
+
)
|
|
1426
|
+
tp_id_loc = api.telephony.call_routing.tp.create(
|
|
1427
|
+
pattern=pattern_loc,
|
|
1428
|
+
location_id=location_id
|
|
1429
|
+
)
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
### CLI Examples
|
|
1433
|
+
|
|
1434
|
+
```bash
|
|
1435
|
+
# List all translation patterns (org-level and location-level)
|
|
1436
|
+
wxcli call-routing list-translation-patterns
|
|
1437
|
+
|
|
1438
|
+
# Filter to org-level patterns only
|
|
1439
|
+
wxcli call-routing list-translation-patterns --limit-to-org-level-enabled true
|
|
1440
|
+
|
|
1441
|
+
# Filter to patterns at a specific location
|
|
1442
|
+
wxcli call-routing list-translation-patterns \
|
|
1443
|
+
--limit-to-location-id Y2lzY29zcGFyazovL_LOC_ID
|
|
1444
|
+
|
|
1445
|
+
# Filter by name or matching pattern
|
|
1446
|
+
wxcli call-routing list-translation-patterns --name "Strip-9-Prefix"
|
|
1447
|
+
wxcli call-routing list-translation-patterns --matching-pattern "9XXX"
|
|
1448
|
+
|
|
1449
|
+
# Get org-level translation pattern details
|
|
1450
|
+
wxcli call-routing show-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID
|
|
1451
|
+
|
|
1452
|
+
# Get location-level translation pattern details (location_id then translation_id)
|
|
1453
|
+
wxcli call-routing show-translation-patterns-call-routing-1 \
|
|
1454
|
+
Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID
|
|
1455
|
+
|
|
1456
|
+
# Create an org-level translation pattern (strip leading 9)
|
|
1457
|
+
wxcli call-routing create-translation-patterns-call-routing \
|
|
1458
|
+
--name "Strip-9-Prefix" \
|
|
1459
|
+
--matching-pattern "9XXX" \
|
|
1460
|
+
--replacement-pattern "XXX"
|
|
1461
|
+
|
|
1462
|
+
# Create a location-level translation pattern
|
|
1463
|
+
wxcli call-routing create-translation-patterns-call-routing-1 Y2lzY29zcGFyazovL_LOC_ID \
|
|
1464
|
+
--name "Local-Rewrite" \
|
|
1465
|
+
--matching-pattern "+1919555XXXX" \
|
|
1466
|
+
--replacement-pattern "555XXXX"
|
|
1467
|
+
|
|
1468
|
+
# Update an org-level translation pattern
|
|
1469
|
+
wxcli call-routing update-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID \
|
|
1470
|
+
--name "Strip-9-Prefix-v2" \
|
|
1471
|
+
--matching-pattern "9XXX" \
|
|
1472
|
+
--replacement-pattern "XXX"
|
|
1473
|
+
|
|
1474
|
+
# Update a location-level translation pattern (location_id then translation_id)
|
|
1475
|
+
wxcli call-routing update-translation-patterns-call-routing-1 \
|
|
1476
|
+
Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID \
|
|
1477
|
+
--name "Local-Rewrite-v2"
|
|
1478
|
+
|
|
1479
|
+
# Delete an org-level translation pattern
|
|
1480
|
+
wxcli call-routing delete-translation-patterns-call-routing Y2lzY29zcGFyazovL_TP_ID --force
|
|
1481
|
+
|
|
1482
|
+
# Delete a location-level translation pattern (location_id then translation_id)
|
|
1483
|
+
wxcli call-routing delete-translation-patterns-call-routing-1 \
|
|
1484
|
+
Y2lzY29zcGFyazovL_LOC_ID Y2lzY29zcGFyazovL_TP_ID --force
|
|
1485
|
+
```
|
|
1486
|
+
|
|
1487
|
+
### Raw HTTP
|
|
1488
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
1489
|
+
|
|
1490
|
+
Translation patterns use the `/callRouting/` prefix (NOT `/premisePstn/`). Org-level and location-level have different URL paths.
|
|
1491
|
+
|
|
1492
|
+
```python
|
|
1493
|
+
BASE = "https://webexapis.com/v1"
|
|
1494
|
+
|
|
1495
|
+
# --- Org-level translation patterns ---
|
|
1496
|
+
|
|
1497
|
+
# List org-level translation patterns
|
|
1498
|
+
tps = api.session.rest_get(
|
|
1499
|
+
f"{BASE}/telephony/config/callRouting/translationPatterns",
|
|
1500
|
+
params={"max": 1000})
|
|
1501
|
+
# tps["translationPatterns"] -> list of dicts
|
|
1502
|
+
|
|
1503
|
+
# Create org-level translation pattern
|
|
1504
|
+
result = api.session.rest_post(
|
|
1505
|
+
f"{BASE}/telephony/config/callRouting/translationPatterns", json={
|
|
1506
|
+
"name": "Strip-9-Prefix",
|
|
1507
|
+
"matchingPattern": "9XXX",
|
|
1508
|
+
"replacementPattern": "XXX"
|
|
1509
|
+
})
|
|
1510
|
+
# result["id"] -> new pattern ID
|
|
1511
|
+
|
|
1512
|
+
# Get org-level translation pattern
|
|
1513
|
+
tp = api.session.rest_get(
|
|
1514
|
+
f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}")
|
|
1515
|
+
|
|
1516
|
+
# Update org-level translation pattern
|
|
1517
|
+
api.session.rest_put(
|
|
1518
|
+
f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}", json={
|
|
1519
|
+
"name": "Strip-9-Prefix-v2",
|
|
1520
|
+
"matchingPattern": "9XXX",
|
|
1521
|
+
"replacementPattern": "XXX"
|
|
1522
|
+
})
|
|
1523
|
+
|
|
1524
|
+
# Delete org-level translation pattern
|
|
1525
|
+
api.session.rest_delete(
|
|
1526
|
+
f"{BASE}/telephony/config/callRouting/translationPatterns/{translation_id}")
|
|
1527
|
+
|
|
1528
|
+
# --- Location-level translation patterns ---
|
|
1529
|
+
|
|
1530
|
+
# Create location-level translation pattern
|
|
1531
|
+
result = api.session.rest_post(
|
|
1532
|
+
f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns", json={
|
|
1533
|
+
"name": "Local-Rewrite",
|
|
1534
|
+
"matchingPattern": "+1919555XXXX",
|
|
1535
|
+
"replacementPattern": "+19196660000"
|
|
1536
|
+
})
|
|
1537
|
+
|
|
1538
|
+
# Get/update/delete location-level: same pattern with /locations/{location_id}/ prefix
|
|
1539
|
+
api.session.rest_get(
|
|
1540
|
+
f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}")
|
|
1541
|
+
api.session.rest_put(
|
|
1542
|
+
f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}",
|
|
1543
|
+
json={...})
|
|
1544
|
+
api.session.rest_delete(
|
|
1545
|
+
f"{BASE}/telephony/config/locations/{location_id}/callRouting/translationPatterns/{translation_id}")
|
|
1546
|
+
```
|
|
1547
|
+
|
|
1548
|
+
**URL summary:**
|
|
1549
|
+
|
|
1550
|
+
| Operation | Method | URL |
|
|
1551
|
+
|-----------|--------|-----|
|
|
1552
|
+
| List (org) | GET | `{BASE}/telephony/config/callRouting/translationPatterns` |
|
|
1553
|
+
| Create (org) | POST | `{BASE}/telephony/config/callRouting/translationPatterns` |
|
|
1554
|
+
| Get (org) | GET | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
|
|
1555
|
+
| Update (org) | PUT | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
|
|
1556
|
+
| Delete (org) | DELETE | `{BASE}/telephony/config/callRouting/translationPatterns/{id}` |
|
|
1557
|
+
| List (location) | GET | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns` |
|
|
1558
|
+
| Create (location) | POST | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns` |
|
|
1559
|
+
| Get (location) | GET | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
|
|
1560
|
+
| Update (location) | PUT | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
|
|
1561
|
+
| Delete (location) | DELETE | `{BASE}/telephony/config/locations/{locId}/callRouting/translationPatterns/{id}` |
|
|
1562
|
+
|
|
1563
|
+
---
|
|
1564
|
+
|
|
1565
|
+
## PSTN Configuration
|
|
1566
|
+
|
|
1567
|
+
The PSTN API manages the PSTN connection settings for a location -- which provider handles calls and how it connects.
|
|
1568
|
+
|
|
1569
|
+
### Data Models
|
|
1570
|
+
|
|
1571
|
+
```python
|
|
1572
|
+
class PSTNType(str, Enum):
|
|
1573
|
+
local_gateway = 'LOCAL_GATEWAY' # Premises-based PSTN
|
|
1574
|
+
non_integrated_ccp = 'NON_INTEGRATED_CCP' # Non-Integrated Cloud Connected PSTN
|
|
1575
|
+
integrated_ccp = 'INTEGRATED_CCP' # Integrated CCP (not API-configurable)
|
|
1576
|
+
cisco_pstn = 'CISCO_PSTN' # Cisco PSTN (not API-configurable)
|
|
1577
|
+
|
|
1578
|
+
class PSTNServiceType(str, Enum):
|
|
1579
|
+
geographic_numbers = 'GEOGRAPHIC_NUMBERS'
|
|
1580
|
+
tollfree_numbers = 'TOLLFREE_NUMBERS'
|
|
1581
|
+
business_texting = 'BUSINESS_TEXTING'
|
|
1582
|
+
contact_center = 'CONTACT_CENTER'
|
|
1583
|
+
service_numbers = 'SERVICE_NUMBERS'
|
|
1584
|
+
non_geographic_numbers = 'NON_GEOGRAPHIC_NUMBERS'
|
|
1585
|
+
mobile_numbers = 'MOBILE_NUMBERS'
|
|
1586
|
+
|
|
1587
|
+
class PSTNConnectionOption(ApiModel):
|
|
1588
|
+
id: Optional[str]
|
|
1589
|
+
display_name: Optional[str]
|
|
1590
|
+
pstn_services: Optional[list[PSTNServiceType]]
|
|
1591
|
+
pstn_connection_type: Optional[PSTNType]
|
|
1592
|
+
route_type: Optional[RouteType] # required if LOCAL_GATEWAY
|
|
1593
|
+
route_id: Optional[str] # trunk ID or route group ID
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
### API Methods
|
|
1597
|
+
|
|
1598
|
+
#### List PSTN Connection Options for a Location
|
|
1599
|
+
|
|
1600
|
+
```python
|
|
1601
|
+
PSTNApi.list(
|
|
1602
|
+
location_id: str,
|
|
1603
|
+
service_types: list[PSTNServiceType] = None,
|
|
1604
|
+
org_id: str = None
|
|
1605
|
+
) -> list[PSTNConnectionOption]
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
Returns all available PSTN connection options for the given location.
|
|
1609
|
+
|
|
1610
|
+
#### Configure PSTN Connection for a Location
|
|
1611
|
+
|
|
1612
|
+
```python
|
|
1613
|
+
PSTNApi.configure(
|
|
1614
|
+
location_id: str,
|
|
1615
|
+
id: str = None, # connection ID -- required for non-integrated CCP
|
|
1616
|
+
premise_route_type: str = None, # 'TRUNK' or 'ROUTE_GROUP' -- required for local gateway
|
|
1617
|
+
premise_route_id: str = None, # trunk or route group ID -- required for local gateway
|
|
1618
|
+
org_id: str = None
|
|
1619
|
+
) -> None
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1622
|
+
**Important**: Only `LOCAL_GATEWAY` and `NON_INTEGRATED_CCP` types can be configured via the API. `INTEGRATED_CCP` and `CISCO_PSTN` must be configured through the Control Hub UI.
|
|
1623
|
+
|
|
1624
|
+
#### Read Current PSTN Connection for a Location
|
|
1625
|
+
|
|
1626
|
+
```python
|
|
1627
|
+
PSTNApi.read(
|
|
1628
|
+
location_id: str,
|
|
1629
|
+
org_id: str = None
|
|
1630
|
+
) -> PSTNConnectionOption
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
### Usage Example
|
|
1634
|
+
|
|
1635
|
+
```python
|
|
1636
|
+
# Set a location to use a local gateway trunk for PSTN
|
|
1637
|
+
api.telephony.pstn.configure(
|
|
1638
|
+
location_id=location_id,
|
|
1639
|
+
premise_route_type='TRUNK',
|
|
1640
|
+
premise_route_id=trunk_id
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
# Or use a route group
|
|
1644
|
+
api.telephony.pstn.configure(
|
|
1645
|
+
location_id=location_id,
|
|
1646
|
+
premise_route_type='ROUTE_GROUP',
|
|
1647
|
+
premise_route_id=rg_id
|
|
1648
|
+
)
|
|
1649
|
+
|
|
1650
|
+
# Read current configuration
|
|
1651
|
+
current = api.telephony.pstn.read(location_id=location_id)
|
|
1652
|
+
print(f"Connection: {current.pstn_connection_type}")
|
|
1653
|
+
print(f"Route: {current.route_type} -> {current.route_id}")
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
### Raw HTTP
|
|
1657
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
1658
|
+
|
|
1659
|
+
PSTN endpoints use the `/telephony/pstn/` prefix (NOT `/premisePstn/` and NOT `/telephony/config/`).
|
|
1660
|
+
|
|
1661
|
+
```python
|
|
1662
|
+
BASE = "https://webexapis.com/v1"
|
|
1663
|
+
|
|
1664
|
+
# List PSTN connection options for a location
|
|
1665
|
+
options = api.session.rest_get(
|
|
1666
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/connectionOptions",
|
|
1667
|
+
params={"max": 1000})
|
|
1668
|
+
# options["connectionOptions"] -> list of dicts
|
|
1669
|
+
|
|
1670
|
+
# Read current PSTN connection for a location
|
|
1671
|
+
conn = api.session.rest_get(
|
|
1672
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/connection")
|
|
1673
|
+
|
|
1674
|
+
# Setup/update PSTN connection for a location (local gateway)
|
|
1675
|
+
api.session.rest_put(
|
|
1676
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/connection", json={
|
|
1677
|
+
"premiseRouteType": "TRUNK",
|
|
1678
|
+
"premiseRouteId": trunk_id
|
|
1679
|
+
})
|
|
1680
|
+
|
|
1681
|
+
# Setup/update PSTN connection (non-integrated CCP)
|
|
1682
|
+
api.session.rest_put(
|
|
1683
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/connection", json={
|
|
1684
|
+
"id": ccp_provider_id
|
|
1685
|
+
})
|
|
1686
|
+
|
|
1687
|
+
# Emergency address lookup
|
|
1688
|
+
result = api.session.rest_post(
|
|
1689
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/emergencyAddress/lookup", json={
|
|
1690
|
+
"address1": "123 Main St",
|
|
1691
|
+
"city": "Raleigh",
|
|
1692
|
+
"state": "NC",
|
|
1693
|
+
"postalCode": "27601",
|
|
1694
|
+
"country": "US"
|
|
1695
|
+
})
|
|
1696
|
+
|
|
1697
|
+
# Add emergency address to location
|
|
1698
|
+
api.session.rest_post(
|
|
1699
|
+
f"{BASE}/telephony/pstn/locations/{location_id}/emergencyAddress", json={
|
|
1700
|
+
"address1": "123 Main St",
|
|
1701
|
+
"city": "Raleigh",
|
|
1702
|
+
"state": "NC",
|
|
1703
|
+
"postalCode": "27601",
|
|
1704
|
+
"country": "US"
|
|
1705
|
+
})
|
|
1706
|
+
|
|
1707
|
+
# Update emergency address for a phone number
|
|
1708
|
+
api.session.rest_put(
|
|
1709
|
+
f"{BASE}/telephony/pstn/numbers/{phone_number}/emergencyAddress", json={...})
|
|
1710
|
+
```
|
|
1711
|
+
|
|
1712
|
+
**URL summary:**
|
|
1713
|
+
|
|
1714
|
+
| Operation | Method | URL |
|
|
1715
|
+
|-----------|--------|-----|
|
|
1716
|
+
| List connection options | GET | `{BASE}/telephony/pstn/locations/{locId}/connectionOptions` |
|
|
1717
|
+
| Read connection | GET | `{BASE}/telephony/pstn/locations/{locId}/connection` |
|
|
1718
|
+
| Setup connection | PUT | `{BASE}/telephony/pstn/locations/{locId}/connection` |
|
|
1719
|
+
| Emergency lookup | POST | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddress/lookup` |
|
|
1720
|
+
| Add emergency addr | POST | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddress` |
|
|
1721
|
+
| Update emergency addr (location) | PUT | `{BASE}/telephony/pstn/locations/{locId}/emergencyAddresses/{addrId}` |
|
|
1722
|
+
| Update emergency addr (number) | PUT | `{BASE}/telephony/pstn/numbers/{phoneNumber}/emergencyAddress` |
|
|
1723
|
+
|
|
1724
|
+
---
|
|
1725
|
+
|
|
1726
|
+
## Premises PSTN
|
|
1727
|
+
|
|
1728
|
+
The `PremisePstnApi` is the parent API that groups all premises-based PSTN sub-APIs and provides the `validate_pattern()` method.
|
|
1729
|
+
|
|
1730
|
+
### SDK Access
|
|
1731
|
+
|
|
1732
|
+
```python
|
|
1733
|
+
api.telephony.prem_pstn # PremisePstnApi
|
|
1734
|
+
api.telephony.prem_pstn.dial_plan # DialPlanApi
|
|
1735
|
+
api.telephony.prem_pstn.trunk # TrunkApi
|
|
1736
|
+
api.telephony.prem_pstn.route_group # RouteGroupApi
|
|
1737
|
+
api.telephony.prem_pstn.route_list # RouteListApi
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
### Validate Dial Pattern
|
|
1741
|
+
|
|
1742
|
+
```python
|
|
1743
|
+
PremisePstnApi.validate_pattern(
|
|
1744
|
+
dial_patterns: Union[str, List[str]],
|
|
1745
|
+
org_id: str = None
|
|
1746
|
+
) -> DialPatternValidationResult
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
Accepts a single pattern string or a list. Returns validation status and per-pattern details.
|
|
1750
|
+
|
|
1751
|
+
```python
|
|
1752
|
+
result = api.telephony.prem_pstn.validate_pattern(['+1408!', '+44!', '9XXX'])
|
|
1753
|
+
if result.ok:
|
|
1754
|
+
print("All patterns valid")
|
|
1755
|
+
else:
|
|
1756
|
+
for p in result.dial_pattern_status:
|
|
1757
|
+
print(f"{p.dial_pattern}: {p.pattern_status} - {p.message}")
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
---
|
|
1761
|
+
|
|
1762
|
+
## Private Network Connect (PNC)
|
|
1763
|
+
|
|
1764
|
+
Private Network Connect determines whether a location uses the public internet or a private network for its connection to Webex Calling.
|
|
1765
|
+
|
|
1766
|
+
### Data Models
|
|
1767
|
+
|
|
1768
|
+
```python
|
|
1769
|
+
class NetworkConnectionType(str, Enum):
|
|
1770
|
+
public_internet = 'PUBLIC_INTERNET'
|
|
1771
|
+
private_network = 'PRIVATE_NETWORK'
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
### API Methods
|
|
1775
|
+
|
|
1776
|
+
#### Read PNC Setting
|
|
1777
|
+
|
|
1778
|
+
```python
|
|
1779
|
+
PrivateNetworkConnectApi.read(
|
|
1780
|
+
location_id: str,
|
|
1781
|
+
org_id: str = None
|
|
1782
|
+
) -> NetworkConnectionType
|
|
1783
|
+
```
|
|
1784
|
+
|
|
1785
|
+
#### Update PNC Setting
|
|
1786
|
+
|
|
1787
|
+
```python
|
|
1788
|
+
PrivateNetworkConnectApi.update(
|
|
1789
|
+
location_id: str,
|
|
1790
|
+
connection_type: NetworkConnectionType,
|
|
1791
|
+
org_id: str = None
|
|
1792
|
+
) -> None
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
### Endpoint
|
|
1796
|
+
|
|
1797
|
+
Both methods use: `telephony/config/locations/{location_id}/privateNetworkConnect`
|
|
1798
|
+
|
|
1799
|
+
### Usage Example
|
|
1800
|
+
|
|
1801
|
+
```python
|
|
1802
|
+
from wxc_sdk.telephony.pnc import NetworkConnectionType
|
|
1803
|
+
|
|
1804
|
+
# Read current setting
|
|
1805
|
+
pnc = api.telephony.pnc.read(location_id=location_id)
|
|
1806
|
+
print(f"Connection type: {pnc}")
|
|
1807
|
+
|
|
1808
|
+
# Switch to private network
|
|
1809
|
+
api.telephony.pnc.update(
|
|
1810
|
+
location_id=location_id,
|
|
1811
|
+
connection_type=NetworkConnectionType.private_network
|
|
1812
|
+
)
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
### Raw HTTP
|
|
1816
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
1817
|
+
|
|
1818
|
+
```python
|
|
1819
|
+
BASE = "https://webexapis.com/v1"
|
|
1820
|
+
|
|
1821
|
+
# Read PNC setting for a location
|
|
1822
|
+
pnc = api.session.rest_get(
|
|
1823
|
+
f"{BASE}/telephony/config/locations/{location_id}/privateNetworkConnect")
|
|
1824
|
+
# pnc["networkConnectionType"] -> "PUBLIC_INTERNET" or "PRIVATE_NETWORK"
|
|
1825
|
+
|
|
1826
|
+
# Update PNC setting
|
|
1827
|
+
api.session.rest_put(
|
|
1828
|
+
f"{BASE}/telephony/config/locations/{location_id}/privateNetworkConnect", json={
|
|
1829
|
+
"networkConnectionType": "PRIVATE_NETWORK"
|
|
1830
|
+
})
|
|
1831
|
+
```
|
|
1832
|
+
|
|
1833
|
+
| Operation | Method | URL |
|
|
1834
|
+
|-----------|--------|-----|
|
|
1835
|
+
| Read PNC | GET | `{BASE}/telephony/config/locations/{locId}/privateNetworkConnect` |
|
|
1836
|
+
| Update PNC | PUT | `{BASE}/telephony/config/locations/{locId}/privateNetworkConnect` |
|
|
1837
|
+
|
|
1838
|
+
---
|
|
1839
|
+
|
|
1840
|
+
## Route Choices
|
|
1841
|
+
|
|
1842
|
+
The `route_choices()` method lists all available routing targets (trunks and route groups) for the organization. This is useful when building dial plans or configuring PSTN connections and you need to enumerate what routes are available.
|
|
1843
|
+
|
|
1844
|
+
### Data Model
|
|
1845
|
+
|
|
1846
|
+
```python
|
|
1847
|
+
class RouteIdentity(ApiModel):
|
|
1848
|
+
route_id: str # alias 'id'
|
|
1849
|
+
name: Optional[str]
|
|
1850
|
+
route_type: RouteType # alias 'type' -- 'ROUTE_GROUP' | 'TRUNK'
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
### API Method
|
|
1854
|
+
|
|
1855
|
+
```python
|
|
1856
|
+
TelephonyApi.route_choices(
|
|
1857
|
+
route_group_name: str = None,
|
|
1858
|
+
trunk_name: str = None,
|
|
1859
|
+
order: str = None, # sort fields: routeName, routeType
|
|
1860
|
+
org_id: str = None
|
|
1861
|
+
) -> Generator[RouteIdentity, None, None]
|
|
1862
|
+
```
|
|
1863
|
+
|
|
1864
|
+
### Usage Example
|
|
1865
|
+
|
|
1866
|
+
```python
|
|
1867
|
+
# List all available route choices
|
|
1868
|
+
for route in api.telephony.route_choices():
|
|
1869
|
+
print(f"{route.name} ({route.route_type}): {route.route_id}")
|
|
1870
|
+
|
|
1871
|
+
# Filter to trunks only
|
|
1872
|
+
for route in api.telephony.route_choices(trunk_name='HQ'):
|
|
1873
|
+
print(f"Trunk: {route.name}")
|
|
1874
|
+
```
|
|
1875
|
+
|
|
1876
|
+
### Raw HTTP
|
|
1877
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
1878
|
+
|
|
1879
|
+
```python
|
|
1880
|
+
BASE = "https://webexapis.com/v1"
|
|
1881
|
+
|
|
1882
|
+
# List all available route choices (trunks + route groups)
|
|
1883
|
+
choices = api.session.rest_get(f"{BASE}/telephony/config/premisePstn/routeChoices",
|
|
1884
|
+
params={"max": 1000})
|
|
1885
|
+
# choices["routeIdentities"] -> list of dicts with id, name, type
|
|
1886
|
+
```
|
|
1887
|
+
|
|
1888
|
+
| Operation | Method | URL |
|
|
1889
|
+
|-----------|--------|-----|
|
|
1890
|
+
| List route choices | GET | `{BASE}/telephony/config/premisePstn/routeChoices` |
|
|
1891
|
+
|
|
1892
|
+
---
|
|
1893
|
+
|
|
1894
|
+
## Call Routing Test
|
|
1895
|
+
|
|
1896
|
+
The `test_call_routing()` method validates that an incoming call can be routed. It simulates the routing logic and returns the destination type and details.
|
|
1897
|
+
|
|
1898
|
+
### Data Models
|
|
1899
|
+
|
|
1900
|
+
```python
|
|
1901
|
+
class OriginatorType(str, Enum):
|
|
1902
|
+
user = 'USER' # originator is a person
|
|
1903
|
+
trunk = 'TRUNK' # originator is a trunk
|
|
1904
|
+
|
|
1905
|
+
class DestinationType(str, Enum):
|
|
1906
|
+
hosted_agent = 'HOSTED_AGENT' # person or workspace
|
|
1907
|
+
hosted_feature = 'HOSTED_FEATURE' # auto-attendant, hunt group, call queue, etc.
|
|
1908
|
+
pbx_user = 'PBX_USER' # on-premises PBX user
|
|
1909
|
+
pstn_number = 'PSTN_NUMBER' # PSTN phone number
|
|
1910
|
+
virtual_extension = 'VIRTUAL_EXTENSION'
|
|
1911
|
+
virtual_extension_range = 'VIRTUAL_EXTENSION_RANGE'
|
|
1912
|
+
route_list = 'ROUTE_LIST'
|
|
1913
|
+
fac = 'FAC' # feature access code
|
|
1914
|
+
emergency = 'EMERGENCY'
|
|
1915
|
+
repair = 'REPAIR' # route in repair state
|
|
1916
|
+
unknown_extension = 'UNKNOWN_EXTENSION'
|
|
1917
|
+
unknown_number = 'UNKNOWN_NUMBER'
|
|
1918
|
+
|
|
1919
|
+
class CallSourceType(str, Enum):
|
|
1920
|
+
route_list = 'ROUTE_LIST'
|
|
1921
|
+
dial_pattern = 'DIAL_PATTERN'
|
|
1922
|
+
unknown_extension = 'UNKNOWN_EXTENSION'
|
|
1923
|
+
unknown_number = 'UNKNOWN_NUMBER'
|
|
1924
|
+
|
|
1925
|
+
class TestCallRoutingResult(ApiModel):
|
|
1926
|
+
language: Optional[str]
|
|
1927
|
+
time_zone: Optional[str]
|
|
1928
|
+
call_source_info: Optional[CallSourceInfo]
|
|
1929
|
+
destination_type: DestinationType
|
|
1930
|
+
routing_address: str # FAC code or routing address
|
|
1931
|
+
outside_access_code: Optional[str]
|
|
1932
|
+
is_rejected: bool
|
|
1933
|
+
hosted_user: Optional[HostedUserDestination] # alias 'hostedAgent'
|
|
1934
|
+
hosted_feature: Optional[HostedFeatureDestination]
|
|
1935
|
+
pbx_user: Optional[PbxUserDestination]
|
|
1936
|
+
pstn_number: Optional[PstnNumberDestination]
|
|
1937
|
+
virtual_extension: Optional[VirtualExtensionDestination]
|
|
1938
|
+
virtual_extension_range: Optional[VirtualExtensionRange]
|
|
1939
|
+
route_list: Optional[RouteListDestination]
|
|
1940
|
+
feature_access_code: Optional[FeatureAccessCodeDestination]
|
|
1941
|
+
emergency: Optional[EmergencyDestination]
|
|
1942
|
+
repair: Optional[TrunkDestination]
|
|
1943
|
+
unknown_extension: Optional[TrunkDestination]
|
|
1944
|
+
unknown_number: Optional[TrunkDestination]
|
|
1945
|
+
applied_services: Optional[list[AppliedService]]
|
|
1946
|
+
```
|
|
1947
|
+
|
|
1948
|
+
#### `CallSourceInfo`
|
|
1949
|
+
|
|
1950
|
+
| Field | Type | Notes |
|
|
1951
|
+
|-------|------|-------|
|
|
1952
|
+
| `call_source_type` | `CallSourceType` | `ROUTE_LIST`, `DIAL_PATTERN`, `UNKNOWN_EXTENSION`, or `UNKNOWN_NUMBER` |
|
|
1953
|
+
| `route_list_name` | `str` | Name of the matched route list (when type is ROUTE_LIST) |
|
|
1954
|
+
| `route_list_id` | `str` | ID of the matched route list |
|
|
1955
|
+
| `dial_plan_name` | `str` | Name of the matched dial plan (when type is DIAL_PATTERN) |
|
|
1956
|
+
| `dial_plan_id` | `str` | ID of the matched dial plan |
|
|
1957
|
+
| `dial_pattern` | `str` | The specific pattern that matched |
|
|
1958
|
+
|
|
1959
|
+
#### `AppliedService`
|
|
1960
|
+
|
|
1961
|
+
| Field | Type | Notes |
|
|
1962
|
+
|-------|------|-------|
|
|
1963
|
+
| `translation_pattern` | `object` | Translation pattern details if applied (see below) |
|
|
1964
|
+
|
|
1965
|
+
Translation pattern sub-object fields:
|
|
1966
|
+
- `matching_pattern`: The pattern that matched the dialed number
|
|
1967
|
+
- `replacement_pattern`: The replacement pattern applied
|
|
1968
|
+
- `matched_number`: The original number that was matched
|
|
1969
|
+
- `translated_number`: The resulting number after translation
|
|
1970
|
+
|
|
1971
|
+
#### Destination-Specific Models
|
|
1972
|
+
|
|
1973
|
+
These models populate the corresponding field on `TestCallRoutingResult` based on the `destination_type`:
|
|
1974
|
+
|
|
1975
|
+
| Model | Key Fields | Notes |
|
|
1976
|
+
|-------|------------|-------|
|
|
1977
|
+
| `HostedUserDestination` | `hosted_user_id`, `last_name`, `first_name`, `extension`, `phone_number`, `location_name` | Person or workspace destination |
|
|
1978
|
+
| `HostedFeatureDestination` | `hosted_feature_id`, `name`, `feature_type`, `extension`, `phone_number`, `location_name` | AA, CQ, HG, or other hosted feature |
|
|
1979
|
+
| `PbxUserDestination` | `dial_plan_name`, `dial_pattern`, `trunk_name`, `route_group_name` | On-premises PBX user routed via trunk |
|
|
1980
|
+
| `PstnNumberDestination` | `trunk_name`, `route_group_name`, `trunk_id`, `route_group_id`, `outside_access_code` | PSTN number routed via trunk/route group |
|
|
1981
|
+
| `VirtualExtensionDestination` | `extension`, `first_name`, `last_name`, `phone_number` | Virtual extension destination |
|
|
1982
|
+
| `VirtualExtensionRange` | `extension`, `prefix`, `range_name` | Virtual extension range destination |
|
|
1983
|
+
| `RouteListDestination` | `route_list_id`, `route_list_name`, `route_group_name`, `trunk_name` | Route list destination |
|
|
1984
|
+
| `FeatureAccessCodeDestination` | `code`, `name` | Feature access code destination |
|
|
1985
|
+
| `EmergencyDestination` | `is_emergency_callback_number` | Emergency services destination |
|
|
1986
|
+
| `TrunkDestination` | `trunk_name`, `trunk_id`, `route_group_name`, `route_group_id` | Used for repair, unknown_extension, unknown_number |
|
|
1987
|
+
|
|
1988
|
+
The `applied_services` field returns details about any translation patterns, call intercept rules, or outgoing calling plan permissions that were applied during routing.
|
|
1989
|
+
|
|
1990
|
+
### API Method
|
|
1991
|
+
|
|
1992
|
+
```python
|
|
1993
|
+
TelephonyApi.test_call_routing(
|
|
1994
|
+
originator_id: str, # person ID or trunk ID
|
|
1995
|
+
originator_type: OriginatorType, # USER or TRUNK
|
|
1996
|
+
destination: str, # any dialable string (E.164, extension, URL, FAC)
|
|
1997
|
+
originator_number: str = None, # phone number/URI -- only when originator_type is TRUNK
|
|
1998
|
+
include_applied_services: bool = None, # include translation patterns, intercept, permissions
|
|
1999
|
+
org_id: str = None
|
|
2000
|
+
) -> TestCallRoutingResult
|
|
2001
|
+
```
|
|
2002
|
+
|
|
2003
|
+
### Usage Example
|
|
2004
|
+
|
|
2005
|
+
```python
|
|
2006
|
+
from wxc_sdk.telephony import OriginatorType
|
|
2007
|
+
|
|
2008
|
+
# Test routing from a user to an external number
|
|
2009
|
+
result = api.telephony.test_call_routing(
|
|
2010
|
+
originator_id=person_id,
|
|
2011
|
+
originator_type=OriginatorType.user,
|
|
2012
|
+
destination='+19195551234',
|
|
2013
|
+
include_applied_services=True
|
|
2014
|
+
)
|
|
2015
|
+
|
|
2016
|
+
print(f"Destination type: {result.destination_type}")
|
|
2017
|
+
print(f"Routing address: {result.routing_address}")
|
|
2018
|
+
print(f"Rejected: {result.is_rejected}")
|
|
2019
|
+
|
|
2020
|
+
if result.destination_type == DestinationType.pstn_number:
|
|
2021
|
+
print(f"Trunk: {result.pstn_number.trunk_name}")
|
|
2022
|
+
print(f"Route Group: {result.pstn_number.route_group_name}")
|
|
2023
|
+
|
|
2024
|
+
if result.applied_services:
|
|
2025
|
+
for svc in result.applied_services:
|
|
2026
|
+
if svc.translation_pattern:
|
|
2027
|
+
tp = svc.translation_pattern
|
|
2028
|
+
print(f"Translation: {tp.matching_pattern} -> {tp.replacement_pattern}")
|
|
2029
|
+
print(f" {tp.matched_number} -> {tp.translated_number}")
|
|
2030
|
+
|
|
2031
|
+
# Test routing from a trunk (inbound from PSTN)
|
|
2032
|
+
result = api.telephony.test_call_routing(
|
|
2033
|
+
originator_id=trunk_id,
|
|
2034
|
+
originator_type=OriginatorType.trunk,
|
|
2035
|
+
destination='+19195551234',
|
|
2036
|
+
originator_number='+14085559999'
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
if result.call_source_info:
|
|
2040
|
+
csi = result.call_source_info
|
|
2041
|
+
print(f"Call source type: {csi.call_source_type}")
|
|
2042
|
+
if csi.route_list_name:
|
|
2043
|
+
print(f"Route list: {csi.route_list_name}")
|
|
2044
|
+
if csi.dial_plan_name:
|
|
2045
|
+
print(f"Dial plan: {csi.dial_plan_name}, Pattern: {csi.dial_pattern}")
|
|
2046
|
+
```
|
|
2047
|
+
|
|
2048
|
+
### CLI Examples
|
|
2049
|
+
|
|
2050
|
+
```bash
|
|
2051
|
+
# Test call routing from a user to an external number
|
|
2052
|
+
wxcli call-routing test-call-routing \
|
|
2053
|
+
--originator-id Y2lzY29zcGFyazovL_PERSON_ID \
|
|
2054
|
+
--destination "+19195551234"
|
|
2055
|
+
|
|
2056
|
+
# Test with applied services (shows translation patterns, intercept, permissions)
|
|
2057
|
+
wxcli call-routing test-call-routing \
|
|
2058
|
+
--originator-id Y2lzY29zcGFyazovL_PERSON_ID \
|
|
2059
|
+
--destination "+19195551234" \
|
|
2060
|
+
--include-applied-services true
|
|
2061
|
+
|
|
2062
|
+
# Test routing from a trunk (inbound from PSTN)
|
|
2063
|
+
wxcli call-routing test-call-routing \
|
|
2064
|
+
--originator-id Y2lzY29zcGFyazovL_TRUNK_ID \
|
|
2065
|
+
--originator-number "+14085559999" \
|
|
2066
|
+
--destination "+19195551234"
|
|
2067
|
+
|
|
2068
|
+
# Test using full JSON body (originatorType defaults to PEOPLE per OpenAPI spec)
|
|
2069
|
+
wxcli call-routing test-call-routing --json-body '{
|
|
2070
|
+
"originatorId": "Y2lzY29zcGFyazovL_PERSON_ID",
|
|
2071
|
+
"originatorType": "PEOPLE",
|
|
2072
|
+
"destination": "+19195551234",
|
|
2073
|
+
"includeAppliedServices": true
|
|
2074
|
+
}'
|
|
2075
|
+
|
|
2076
|
+
# Test inbound from trunk via JSON body
|
|
2077
|
+
wxcli call-routing test-call-routing --json-body '{
|
|
2078
|
+
"originatorId": "Y2lzY29zcGFyazovL_TRUNK_ID",
|
|
2079
|
+
"originatorType": "TRUNK",
|
|
2080
|
+
"destination": "+19195551234",
|
|
2081
|
+
"originatorNumber": "+14085559999"
|
|
2082
|
+
}'
|
|
2083
|
+
```
|
|
2084
|
+
|
|
2085
|
+
### Raw HTTP
|
|
2086
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
2087
|
+
|
|
2088
|
+
Test call routing uses an action endpoint under `/telephony/config/` (NOT `/premisePstn/`).
|
|
2089
|
+
|
|
2090
|
+
```python
|
|
2091
|
+
BASE = "https://webexapis.com/v1"
|
|
2092
|
+
|
|
2093
|
+
# Test call routing (POST, not GET)
|
|
2094
|
+
result = api.session.rest_post(
|
|
2095
|
+
f"{BASE}/telephony/config/actions/testCallRouting/invoke", json={
|
|
2096
|
+
"originatorId": person_id,
|
|
2097
|
+
"originatorType": "USER",
|
|
2098
|
+
"destination": "+19195551234",
|
|
2099
|
+
"includeAppliedServices": True
|
|
2100
|
+
})
|
|
2101
|
+
# result["destinationType"] -> "HOSTED_AGENT", "PSTN_NUMBER", etc.
|
|
2102
|
+
# result["routingAddress"] -> the resolved routing address
|
|
2103
|
+
# result["isRejected"] -> boolean
|
|
2104
|
+
|
|
2105
|
+
# Test from trunk (inbound from PSTN)
|
|
2106
|
+
result = api.session.rest_post(
|
|
2107
|
+
f"{BASE}/telephony/config/actions/testCallRouting/invoke", json={
|
|
2108
|
+
"originatorId": trunk_id,
|
|
2109
|
+
"originatorType": "TRUNK",
|
|
2110
|
+
"destination": "+19195551234",
|
|
2111
|
+
"originatorNumber": "+14085559999"
|
|
2112
|
+
})
|
|
2113
|
+
```
|
|
2114
|
+
|
|
2115
|
+
**Note:** The `originatorType` value differs between sources. The OpenAPI spec defines `OriginatorType` as `["PEOPLE", "TRUNK"]`, while wxc_sdk uses `"USER"` and `"TRUNK"`. The live API accepts **both** `"PEOPLE"` and `"USER"` as valid values for `originatorType` and returns identical results. Use `"PEOPLE"` for new code (matches OpenAPI spec), but `"USER"` (wxc_sdk convention) also works.
|
|
2116
|
+
|
|
2117
|
+
| Operation | Method | URL |
|
|
2118
|
+
|-----------|--------|-----|
|
|
2119
|
+
| Test call routing | POST | `{BASE}/telephony/config/actions/testCallRouting/invoke` |
|
|
2120
|
+
|
|
2121
|
+
---
|
|
2122
|
+
|
|
2123
|
+
## Phone Number Management
|
|
2124
|
+
|
|
2125
|
+
### List Phone Numbers
|
|
2126
|
+
|
|
2127
|
+
```python
|
|
2128
|
+
TelephonyApi.phone_numbers(
|
|
2129
|
+
location_id: str = None,
|
|
2130
|
+
phone_number: str = None,
|
|
2131
|
+
available: bool = None,
|
|
2132
|
+
order: str = None, # sort: lastName, dn, extension
|
|
2133
|
+
owner_name: str = None,
|
|
2134
|
+
owner_id: str = None,
|
|
2135
|
+
owner_type: OwnerType = None,
|
|
2136
|
+
extension: str = None,
|
|
2137
|
+
number_type: NumberType = None, # EXTENSION | NUMBER
|
|
2138
|
+
phone_number_type: NumberListPhoneNumberType = None, # PRIMARY | ALTERNATE | FAX | DNIS
|
|
2139
|
+
state: NumberState = None, # ACTIVE | INACTIVE
|
|
2140
|
+
details: bool = None,
|
|
2141
|
+
toll_free_numbers: bool = None,
|
|
2142
|
+
restricted_non_geo_numbers: bool = None,
|
|
2143
|
+
included_telephony_type: TelephonyType = None, # PSTN_NUMBER | MOBILE_NUMBER
|
|
2144
|
+
service_number: bool = None,
|
|
2145
|
+
org_id: str = None
|
|
2146
|
+
) -> Generator[NumberListPhoneNumber, None, None]
|
|
2147
|
+
```
|
|
2148
|
+
|
|
2149
|
+
Numbers can be standard, service, or mobile. Both standard and service numbers are PSTN numbers. Service numbers are high-utilization numbers assignable to features (auto-attendants, call queues, hunt groups).
|
|
2150
|
+
|
|
2151
|
+
### Validate Phone Numbers
|
|
2152
|
+
|
|
2153
|
+
```python
|
|
2154
|
+
TelephonyApi.validate_phone_numbers(
|
|
2155
|
+
phone_numbers: list[str],
|
|
2156
|
+
org_id: str = None
|
|
2157
|
+
) -> ValidatePhoneNumbersResponse
|
|
2158
|
+
```
|
|
2159
|
+
|
|
2160
|
+
```python
|
|
2161
|
+
class ValidatePhoneNumbersResponse(ApiModel):
|
|
2162
|
+
status: ValidationStatus # 'OK' | 'ERRORS'
|
|
2163
|
+
phone_numbers: Optional[list[ValidatePhoneNumberStatus]]
|
|
2164
|
+
# .ok property: True if status == 'OK'
|
|
2165
|
+
|
|
2166
|
+
class ValidatePhoneNumberStatus(ApiModel):
|
|
2167
|
+
phone_number: str
|
|
2168
|
+
state: ValidatePhoneNumberStatusState # Available | Duplicate | Duplicate In List | Invalid | Unavailable
|
|
2169
|
+
toll_free_number: bool
|
|
2170
|
+
detail: list[str]
|
|
2171
|
+
# .ok property: True if state == Available
|
|
2172
|
+
```
|
|
2173
|
+
|
|
2174
|
+
Phone numbers must follow **E.164 format** for all countries, except for the United States which can also use National format.
|
|
2175
|
+
|
|
2176
|
+
### Usage Example
|
|
2177
|
+
|
|
2178
|
+
```python
|
|
2179
|
+
# Validate numbers before provisioning
|
|
2180
|
+
result = api.telephony.validate_phone_numbers(
|
|
2181
|
+
phone_numbers=['+19195551234', '+19195555678']
|
|
2182
|
+
)
|
|
2183
|
+
if result.ok:
|
|
2184
|
+
print("All numbers available")
|
|
2185
|
+
else:
|
|
2186
|
+
for pn in result.phone_numbers:
|
|
2187
|
+
if not pn.ok:
|
|
2188
|
+
print(f"{pn.phone_number}: {pn.state} - {pn.detail}")
|
|
2189
|
+
|
|
2190
|
+
# List all assigned numbers at a location
|
|
2191
|
+
for num in api.telephony.phone_numbers(location_id=location_id, available=False):
|
|
2192
|
+
owner_name = num.owner.last_name if num.owner else 'Unassigned'
|
|
2193
|
+
print(f"{num.phone_number} ext:{num.extension} -> {owner_name}")
|
|
2194
|
+
```
|
|
2195
|
+
|
|
2196
|
+
### Raw HTTP
|
|
2197
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
2198
|
+
|
|
2199
|
+
```python
|
|
2200
|
+
BASE = "https://webexapis.com/v1"
|
|
2201
|
+
|
|
2202
|
+
# List phone numbers (no auto-pagination -- use max=1000)
|
|
2203
|
+
numbers = api.session.rest_get(f"{BASE}/telephony/config/numbers",
|
|
2204
|
+
params={"max": 1000, "locationId": location_id})
|
|
2205
|
+
# numbers["phoneNumbers"] -> list of dicts
|
|
2206
|
+
|
|
2207
|
+
# Validate phone numbers
|
|
2208
|
+
result = api.session.rest_post(f"{BASE}/telephony/config/actions/validateNumbers/invoke",
|
|
2209
|
+
json={"phoneNumbers": ["+19195551234", "+19195555678"]})
|
|
2210
|
+
# result["status"] -> "OK" or "ERRORS"
|
|
2211
|
+
# result["phoneNumbers"] -> list of validation status dicts
|
|
2212
|
+
```
|
|
2213
|
+
|
|
2214
|
+
| Operation | Method | URL |
|
|
2215
|
+
|-----------|--------|-----|
|
|
2216
|
+
| List numbers | GET | `{BASE}/telephony/config/numbers` |
|
|
2217
|
+
| Validate numbers | POST | `{BASE}/telephony/config/actions/validateNumbers/invoke` |
|
|
2218
|
+
|
|
2219
|
+
---
|
|
2220
|
+
|
|
2221
|
+
## Data Models Quick Reference
|
|
2222
|
+
|
|
2223
|
+
### Enums
|
|
2224
|
+
|
|
2225
|
+
| Enum | Values | Used In |
|
|
2226
|
+
|------|--------|---------|
|
|
2227
|
+
| `RouteType` | `ROUTE_GROUP`, `TRUNK`, `CISCO_PSTN`, `CLOUD_CONNECTED_PSTN` | Dial plans, PSTN config |
|
|
2228
|
+
| `TrunkType` | `REGISTERING`, `CERTIFICATE_BASED` | Trunk creation |
|
|
2229
|
+
| `PatternAction` | `ADD`, `DELETE` | Dial pattern and route list number modifications |
|
|
2230
|
+
| `ValidationStatus` | `OK`, `ERRORS` | Pattern and number validation results |
|
|
2231
|
+
| `DialPatternStatus` | `INVALID`, `DUPLICATE`, `DUPLICATE_IN_LIST` | Pattern validation errors |
|
|
2232
|
+
| `DeviceStatus` | `online`, `offline`, `unknown` | Trunk detail status |
|
|
2233
|
+
| `PSTNType` | `LOCAL_GATEWAY`, `NON_INTEGRATED_CCP`, `INTEGRATED_CCP`, `CISCO_PSTN` | PSTN connection config |
|
|
2234
|
+
| `NetworkConnectionType` | `PUBLIC_INTERNET`, `PRIVATE_NETWORK` | PNC settings |
|
|
2235
|
+
| `OriginatorType` | `USER`, `TRUNK` | Call routing test |
|
|
2236
|
+
| `DestinationType` | `HOSTED_AGENT`, `HOSTED_FEATURE`, `PBX_USER`, `PSTN_NUMBER`, `VIRTUAL_EXTENSION`, `VIRTUAL_EXTENSION_RANGE`, `ROUTE_LIST`, `FAC`, `EMERGENCY`, `REPAIR`, `UNKNOWN_EXTENSION`, `UNKNOWN_NUMBER` | Call routing test result |
|
|
2237
|
+
| `TranslationPatternLevel` | `Location`, `Organization` | Translation patterns |
|
|
2238
|
+
| `NumberState` | `ACTIVE`, `INACTIVE` | Phone number listing |
|
|
2239
|
+
| `PChargeInfoSupportPolicy` | `DISABLED`, `ASSERTED_IDENTITY`, `CONFIGURABLE_CHARGE_NUMBER` | Trunk config |
|
|
2240
|
+
|
|
2241
|
+
### Complete End-to-End Setup Flow
|
|
2242
|
+
|
|
2243
|
+
The typical order for setting up premises-based PSTN routing:
|
|
2244
|
+
|
|
2245
|
+
1. **Create Trunk(s)** -- establish SIP connections to on-premises gateways
|
|
2246
|
+
2. **Create Route Group** (optional) -- bundle trunks for failover
|
|
2247
|
+
3. **Create Dial Plan** -- define patterns and associate with trunk or route group
|
|
2248
|
+
4. **Create Translation Patterns** (optional) -- digit manipulation before routing
|
|
2249
|
+
5. **Configure PSTN Connection** -- point location to trunk or route group
|
|
2250
|
+
6. **Create Route List** (optional) -- for Dedicated Instance cloud PSTN
|
|
2251
|
+
7. **Validate** -- use `test_call_routing()` to verify the configuration
|
|
2252
|
+
|
|
2253
|
+
```
|
|
2254
|
+
Step 1: Trunk (SBC/LGW)
|
|
2255
|
+
|
|
|
2256
|
+
Step 2: Route Group (optional, for failover)
|
|
2257
|
+
|
|
|
2258
|
+
Step 3: Dial Plan (pattern matching → route choice)
|
|
2259
|
+
|
|
|
2260
|
+
Step 4: Translation Pattern (optional, digit rewrite)
|
|
2261
|
+
|
|
|
2262
|
+
Step 5: PSTN Connection (location → trunk/route group)
|
|
2263
|
+
|
|
|
2264
|
+
Step 6: Route List (optional, Dedicated Instance)
|
|
2265
|
+
|
|
|
2266
|
+
Step 7: test_call_routing() to validate
|
|
2267
|
+
```
|
|
2268
|
+
|
|
2269
|
+
---
|
|
2270
|
+
|
|
2271
|
+
## Common Gotchas
|
|
2272
|
+
|
|
2273
|
+
### 0. Raw HTTP URLs require the `/premisePstn/` prefix for routing resources
|
|
2274
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
2275
|
+
|
|
2276
|
+
Dial plans, trunks, route groups, and route lists all live under `telephony/config/premisePstn/` -- NOT under `telephony/config/dialPlans` or similar. Translation patterns use a different prefix: `telephony/config/callRouting/translationPatterns`. PSTN connection uses yet another: `telephony/pstn/locations/`. Test call routing uses `telephony/config/actions/testCallRouting/invoke`. Getting any of these prefixes wrong returns 404.
|
|
2277
|
+
|
|
2278
|
+
### 1. Translation pattern replacement must use fully specified digits
|
|
2279
|
+
|
|
2280
|
+
Translation pattern replacement strings cannot contain `X` wildcards in any format. For example, `+1919666XXXX` is rejected, and so is `XXX` (non-E.164). Use fully specified literal digits: `+19196660000`. The matching pattern accepts `X` wildcards normally (`9XXXXXXX`, `+1512555XXXX`), but the replacement must be all literal digits. Confirmed via live API testing 2026-04-18 — error 28043 "Invalid Translation Replacement Pattern" for any replacement containing `X`.
|
|
2281
|
+
|
|
2282
|
+
### 2. Dial plans require an existing trunk or route group
|
|
2283
|
+
|
|
2284
|
+
You cannot create a standalone dial plan without a route choice. The dial plan must reference an existing trunk or route group as its route choice at creation time.
|
|
2285
|
+
|
|
2286
|
+
### 3. Test Call Routing requires a calling-enabled user's originatorId
|
|
2287
|
+
|
|
2288
|
+
The `test_call_routing` API requires the `originatorId` to be a valid calling-enabled user. Passing a non-calling user's ID returns `404 "Originator not found"`. Always verify the user has a Webex Calling license and location assigned before using them as an originator.
|
|
2289
|
+
|
|
2290
|
+
### 4. Trunk passwords reject `?` and `!` characters
|
|
2291
|
+
|
|
2292
|
+
When creating a REGISTERING trunk, the password field rejects `?` and `!` — error 25015 "Invalid characters ? or ! in password." Use alphanumeric + other special characters (e.g., `@`, `#`, `$`). Confirmed via live API testing 2026-04-18.
|
|
2293
|
+
|
|
2294
|
+
### 5. Number porting and ordering are Control Hub only
|
|
2295
|
+
|
|
2296
|
+
The API manages PSTN connections, trunks, and routing for numbers that already exist in the org. To port in new numbers or order from Cisco Calling Plan, use Control Hub or contact Cisco PTS.
|
|
2297
|
+
|
|
2298
|
+
---
|
|
2299
|
+
|
|
2300
|
+
## See Also
|
|
2301
|
+
|
|
2302
|
+
- [Major Call Features](call-features-major.md) -- Auto Attendants, Call Queues, and Hunt Groups (the `HOSTED_FEATURE` destination type in call routing test results covers these features)
|
|
2303
|
+
- [Provisioning Reference](provisioning.md) -- creating locations and users (trunks, route lists, and PSTN connections are all location-scoped)
|
|
2304
|
+
- [Devices Reference](devices-core.md) -- device types and device management (relevant to trunk device type selection)
|