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,1229 @@
|
|
|
1
|
+
# wxc_sdk Patterns & Recipes
|
|
2
|
+
|
|
3
|
+
Reference document for the `wxc_sdk` Python SDK -- the primary tool for automating Webex Calling administration via the Webex REST APIs.
|
|
4
|
+
|
|
5
|
+
**SDK Version (at time of writing):** `1.30.0`
|
|
6
|
+
|
|
7
|
+
## Sources
|
|
8
|
+
|
|
9
|
+
- **wxc_sdk v1.30.0** — [github.com/jeokrohn/wxc_sdk](https://github.com/jeokrohn/wxc_sdk)
|
|
10
|
+
- **Docs** — [wxc-sdk.readthedocs.io](http://wxc-sdk.readthedocs.io)
|
|
11
|
+
- **Webex API Reference** — [developer.webex.com](https://developer.webex.com/docs/api/getting-started)
|
|
12
|
+
|
|
13
|
+
## Required Scopes
|
|
14
|
+
|
|
15
|
+
Token type and scope requirements for common SDK operations. Admin tokens (`spark-admin:*`) cover org-wide management; user tokens (`spark:*`) cover personal settings and call control.
|
|
16
|
+
|
|
17
|
+
| Operation Category | Required Scope | Token Type | Notes |
|
|
18
|
+
|-------------------|----------------|------------|-------|
|
|
19
|
+
| List/read people | `spark-admin:people_read` | Admin / Service App | `calling_data=True` requires this scope |
|
|
20
|
+
| Create/update/delete people | `spark-admin:people_write` | Admin / Service App | Also needs `_read` for pre-flight checks |
|
|
21
|
+
| List/read locations | `spark-admin:locations_read` | Admin / Service App | |
|
|
22
|
+
| Manage locations | `spark-admin:locations_write` | Admin / Service App | |
|
|
23
|
+
| Read telephony config (queues, HG, AA, etc.) | `spark-admin:telephony_config_read` | Admin / Service App | Covers all `/telephony/config/*` endpoints |
|
|
24
|
+
| Write telephony config | `spark-admin:telephony_config_write` | Admin / Service App | Create/update/delete calling features |
|
|
25
|
+
| Read person call settings | `spark-admin:people_read` | Admin / Service App | Voicemail, forwarding, caller ID, etc. |
|
|
26
|
+
| Write person call settings | `spark-admin:people_write` | Admin / Service App | |
|
|
27
|
+
| Read workspace settings | `spark-admin:workspaces_read` | Admin / Service App | |
|
|
28
|
+
| Manage workspaces | `spark-admin:workspaces_write` | Admin / Service App | |
|
|
29
|
+
| Read devices | `spark-admin:devices_read` | Admin / Service App | |
|
|
30
|
+
| Manage devices | `spark-admin:devices_write` | Admin / Service App | |
|
|
31
|
+
| Read licenses | `spark-admin:licenses_read` | Admin / Service App | |
|
|
32
|
+
| Call control (dial, answer, hold, transfer) | `spark:calls_write` | **User** | Must be calling-licensed user's token; admin tokens get 400 |
|
|
33
|
+
| Read own call settings (`/people/me/*`) | `spark:people_read` | **User** | User must have Webex Calling license |
|
|
34
|
+
| CDR / call detail records | `spark-admin:cdr_read` | Admin / Service App | |
|
|
35
|
+
| Webhooks | `spark:webhooks_read`, `spark:webhooks_write` | Admin or User | |
|
|
36
|
+
| Admin audit events | `spark-admin:audit_events_read` | Admin / Service App | |
|
|
37
|
+
| Org/roles/authorizations | `spark-admin:organizations_read` | Admin / Service App | |
|
|
38
|
+
|
|
39
|
+
**Scope naming pattern:** `spark-admin:` prefix = org-wide admin scope; `spark:` prefix = user-level scope. Most SDK operations use admin scopes. Call control and `/people/me/*` endpoints are the main exceptions requiring user-level tokens.
|
|
40
|
+
|
|
41
|
+
## Table of Contents
|
|
42
|
+
|
|
43
|
+
1. [Installation](#1-installation)
|
|
44
|
+
2. [Raw HTTP Pattern](#15-raw-http-pattern-preferred-execution-method)
|
|
45
|
+
3. [WebexSimpleApi -- The Sync Entry Point](#2-webexsimpleapi----the-sync-entry-point)
|
|
46
|
+
4. [Authentication Patterns](#3-authentication-patterns)
|
|
47
|
+
5. [Sync vs Async](#4-sync-vs-async)
|
|
48
|
+
6. [Common Recipes](#5-common-recipes)
|
|
49
|
+
7. [Error Handling](#6-error-handling)
|
|
50
|
+
8. [REST Client Details](#7-rest-client-details)
|
|
51
|
+
9. [Data Types](#8-data-types)
|
|
52
|
+
10. [Environment Variable Patterns](#9-environment-variable-patterns)
|
|
53
|
+
11. [Logging](#10-logging)
|
|
54
|
+
12. [Key Gotchas](#11-key-gotchas)
|
|
55
|
+
13. [See Also](#see-also)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 1. Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Install
|
|
63
|
+
pip install wxc-sdk
|
|
64
|
+
|
|
65
|
+
# Upgrade
|
|
66
|
+
pip install wxc-sdk --upgrade
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Common companion packages used in the examples:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install python-dotenv # .env file loading
|
|
73
|
+
pip install pyyaml # token caching to YML
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Core dependencies pulled in automatically by wxc_sdk:
|
|
77
|
+
- `requests` (sync HTTP -- powers `RestSession`)
|
|
78
|
+
- `aiohttp` (async HTTP -- powers `AsRestSession`)
|
|
79
|
+
- `pydantic` (data models)
|
|
80
|
+
- `aenum` (dynamic enum extension)
|
|
81
|
+
- `python-dateutil` / `pytz` (date handling)
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 1.5. Raw HTTP Pattern (Preferred Execution Method)
|
|
86
|
+
<!-- Updated by playbook session 2026-03-18 -->
|
|
87
|
+
|
|
88
|
+
The primary way to execute Webex API calls in this project is **raw HTTP via `api.session.rest_*()`**. We use `wxc_sdk` for authentication and session management only -- all actual API calls go through the REST client methods with full URLs.
|
|
89
|
+
|
|
90
|
+
### Why Raw HTTP
|
|
91
|
+
|
|
92
|
+
- **Full control** over request/response -- no SDK model translation surprises
|
|
93
|
+
- **Matches the Webex API docs exactly** -- URLs, params, and body keys are 1:1 with developer.webex.com
|
|
94
|
+
- **No SDK version dependency** for API coverage -- new endpoints work immediately
|
|
95
|
+
- **Responses are plain dicts** -- easy to inspect, log, and forward
|
|
96
|
+
|
|
97
|
+
### Setup
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from wxc_sdk import WebexSimpleApi
|
|
101
|
+
|
|
102
|
+
api = WebexSimpleApi() # auth via WEBEX_ACCESS_TOKEN env var
|
|
103
|
+
BASE = "https://webexapis.com/v1"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### REST Methods
|
|
107
|
+
|
|
108
|
+
| Method | Signature | Returns |
|
|
109
|
+
|--------|-----------|---------|
|
|
110
|
+
| `rest_get` | `api.session.rest_get(url, params=dict)` | Parsed JSON dict |
|
|
111
|
+
| `rest_post` | `api.session.rest_post(url, json=dict)` | Parsed JSON dict |
|
|
112
|
+
| `rest_put` | `api.session.rest_put(url, json=dict)` | Parsed JSON dict (or None) |
|
|
113
|
+
| `rest_delete` | `api.session.rest_delete(url)` | None |
|
|
114
|
+
|
|
115
|
+
All methods require **full URLs** (`https://webexapis.com/v1/...`), not relative paths.
|
|
116
|
+
|
|
117
|
+
### CRUD Example
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from wxc_sdk import WebexSimpleApi
|
|
121
|
+
from wxc_sdk.rest import RestError
|
|
122
|
+
|
|
123
|
+
api = WebexSimpleApi()
|
|
124
|
+
BASE = "https://webexapis.com/v1"
|
|
125
|
+
|
|
126
|
+
# ── LIST (GET with params) ────────────────────────────────────────
|
|
127
|
+
result = api.session.rest_get(f"{BASE}/people", params={"max": 1000})
|
|
128
|
+
people = result.get("items", [])
|
|
129
|
+
|
|
130
|
+
# ── CREATE (POST with JSON body) ──────────────────────────────────
|
|
131
|
+
body = {
|
|
132
|
+
"emails": ["jsmith@example.com"],
|
|
133
|
+
"displayName": "John Smith",
|
|
134
|
+
"firstName": "John",
|
|
135
|
+
"lastName": "Smith",
|
|
136
|
+
}
|
|
137
|
+
result = api.session.rest_post(f"{BASE}/people", json=body)
|
|
138
|
+
person_id = result["id"]
|
|
139
|
+
|
|
140
|
+
# ── READ (GET by ID) ──────────────────────────────────────────────
|
|
141
|
+
person = api.session.rest_get(f"{BASE}/people/{person_id}")
|
|
142
|
+
|
|
143
|
+
# ── UPDATE (PUT with full body) ───────────────────────────────────
|
|
144
|
+
person["department"] = "Engineering"
|
|
145
|
+
api.session.rest_put(f"{BASE}/people/{person_id}", json=person)
|
|
146
|
+
|
|
147
|
+
# ── DELETE ─────────────────────────────────────────────────────────
|
|
148
|
+
api.session.rest_delete(f"{BASE}/people/{person_id}")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Pagination
|
|
152
|
+
|
|
153
|
+
There is **no automatic pagination** with raw HTTP. The SDK's `follow_pagination()` only works with the typed API methods (e.g., `api.people.list()`). For raw HTTP, request the maximum page size and handle paging manually if needed:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# Request max page size (API maximum is typically 1000)
|
|
157
|
+
result = api.session.rest_get(f"{BASE}/people", params={"max": 1000})
|
|
158
|
+
items = result.get("items", [])
|
|
159
|
+
|
|
160
|
+
# For endpoints that may return more than 1000 items,
|
|
161
|
+
# check for a next link (rare in practice for most Webex APIs):
|
|
162
|
+
# The SDK does NOT auto-follow next links in raw HTTP mode.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Error Handling
|
|
166
|
+
|
|
167
|
+
All `rest_*()` methods raise `RestError` on HTTP errors. The exception carries the full error detail from the Webex API:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from wxc_sdk.rest import RestError
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
api.session.rest_get(f"{BASE}/people/invalid-id")
|
|
174
|
+
except RestError as e:
|
|
175
|
+
print(f"HTTP {e.response.status_code}") # e.g., 404
|
|
176
|
+
print(f"Error code: {e.code}") # Webex error code
|
|
177
|
+
print(f"Description: {e.description}") # Human-readable message
|
|
178
|
+
print(f"Tracking ID: {e.detail.tracking_id}") # For Cisco TAC
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Response Parsing
|
|
182
|
+
|
|
183
|
+
Responses are already-parsed JSON dicts. The response key varies by endpoint:
|
|
184
|
+
|
|
185
|
+
| Endpoint | List response key |
|
|
186
|
+
|----------|------------------|
|
|
187
|
+
| `/v1/people` | `items` |
|
|
188
|
+
| `/v1/locations` | `items` |
|
|
189
|
+
| `/v1/licenses` | `items` |
|
|
190
|
+
| `/v1/workspaces` | `items` |
|
|
191
|
+
| `/v1/telephony/config/numbers` | `phoneNumbers` |
|
|
192
|
+
| `/v1/telephony/config/jobs/numbers/manageNumbers` | `items` |
|
|
193
|
+
|
|
194
|
+
Single-resource GETs return the resource dict directly (no wrapper key).
|
|
195
|
+
|
|
196
|
+
### When to Use Typed SDK Methods Instead
|
|
197
|
+
|
|
198
|
+
The typed SDK API (e.g., `api.people.list()`) is still useful for:
|
|
199
|
+
- **Auto-pagination** -- `list()` returns generators that follow all pages automatically
|
|
200
|
+
- **Pydantic models** -- typed access with IDE completion and validation
|
|
201
|
+
- **Async bulk operations** -- `AsWebexSimpleApi` with `asyncio.gather` for high-concurrency reads
|
|
202
|
+
|
|
203
|
+
The existing SDK patterns in this doc remain valid. Raw HTTP is the **preferred approach for write operations and targeted reads** where you want direct control over the request.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 2. WebexSimpleApi -- The Sync Entry Point
|
|
208
|
+
|
|
209
|
+
`WebexSimpleApi` is the main class. It creates a `RestSession` and attaches every sub-API as a property.
|
|
210
|
+
|
|
211
|
+
### Constructor
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from wxc_sdk import WebexSimpleApi
|
|
215
|
+
|
|
216
|
+
api = WebexSimpleApi(
|
|
217
|
+
tokens=None, # str | Tokens | None
|
|
218
|
+
concurrent_requests=10, # max parallel threads (Semaphore)
|
|
219
|
+
retry_429=True, # auto-retry on rate limit
|
|
220
|
+
# session=RestSession(...) # pass your own session (advanced)
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Token resolution order:**
|
|
225
|
+
1. If `tokens` is a `str`, it is wrapped in `Tokens(access_token=tokens)`.
|
|
226
|
+
2. If `tokens` is a `Tokens` instance, it is used directly.
|
|
227
|
+
3. If `tokens` is `None`, the SDK reads `WEBEX_ACCESS_TOKEN` from the environment.
|
|
228
|
+
4. If the environment variable is also missing, a `ValueError` is raised.
|
|
229
|
+
|
|
230
|
+
### Context Manager
|
|
231
|
+
|
|
232
|
+
`WebexSimpleApi` supports `with` statements. The `__exit__` method calls `session.close()`:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
with WebexSimpleApi() as api:
|
|
236
|
+
users = list(api.people.list())
|
|
237
|
+
# session is closed here
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Top-Level API Properties
|
|
241
|
+
|
|
242
|
+
Every property below is an API sub-object. The ones most relevant to Webex Calling are marked with **(WxC)**.
|
|
243
|
+
|
|
244
|
+
| Property | Class | Notes |
|
|
245
|
+
|----------|-------|-------|
|
|
246
|
+
| `api.people` | `PeopleApi` | **(WxC)** List/get users, `calling_data=True` |
|
|
247
|
+
| `api.locations` | `LocationsApi` | **(WxC)** List/manage locations |
|
|
248
|
+
| `api.telephony` | `TelephonyApi` | **(WxC)** Main Calling API -- call queues, hunt groups, auto attendants, schedules, phone numbers, devices, permissions, virtual lines, etc. |
|
|
249
|
+
| `api.person_settings` | `PersonSettingsApi` | **(WxC)** Per-user calling settings -- voicemail, forwarding, devices, caller ID, etc. |
|
|
250
|
+
| `api.workspace_settings` | `WorkspaceSettingsApi` | **(WxC)** Per-workspace calling settings |
|
|
251
|
+
| `api.workspaces` | `WorkspacesApi` | **(WxC)** Workspace CRUD |
|
|
252
|
+
| `api.devices` | `DevicesApi` | **(WxC)** Device management |
|
|
253
|
+
| `api.licenses` | `LicensesApi` | **(WxC)** License inventory |
|
|
254
|
+
| `api.jobs` | `JobsApi` | **(WxC)** Bulk provisioning jobs |
|
|
255
|
+
| `api.workspace_locations` | `WorkspaceLocationApi` | Workspace locations |
|
|
256
|
+
| `api.workspace_personalization` | `WorkspacePersonalizationApi` | Workspace personalization |
|
|
257
|
+
| `api.admin_audit` | `AdminAuditEventsApi` | Audit trail |
|
|
258
|
+
| `api.authorizations` | `AuthorizationsApi` | User authorizations/tokens |
|
|
259
|
+
| `api.cdr` | `DetailedCDRApi` | Call detail records |
|
|
260
|
+
| `api.converged_recordings` | `ConvergedRecordingsApi` | Recordings |
|
|
261
|
+
| `api.device_configurations` | `DeviceConfigurationsApi` | Device configs |
|
|
262
|
+
| `api.events` | `EventsApi` | Webhooks events |
|
|
263
|
+
| `api.groups` | `GroupsApi` | Groups |
|
|
264
|
+
| `api.guests` | `GuestManagementApi` | Guest management |
|
|
265
|
+
| `api.me` | `MeSettingsApi` | Calling settings for authenticated user |
|
|
266
|
+
| `api.meetings` | `MeetingsApi` | Meetings |
|
|
267
|
+
| `api.membership` | `MembershipApi` | Space memberships |
|
|
268
|
+
| `api.messages` | `MessagesApi` | Messages |
|
|
269
|
+
| `api.org_contacts` | `OrganizationContactsApi` | Org contacts |
|
|
270
|
+
| `api.organizations` | `OrganizationApi` | Org settings |
|
|
271
|
+
| `api.reports` | `ReportsApi` | Reports |
|
|
272
|
+
| `api.roles` | `RolesApi` | Roles |
|
|
273
|
+
| `api.rooms` | `RoomsApi` | Spaces/rooms |
|
|
274
|
+
| `api.room_tabs` | `RoomTabsApi` | Room tabs |
|
|
275
|
+
| `api.scim` | `ScimV2Api` | SCIM v2 |
|
|
276
|
+
| `api.status` | `StatusAPI` | Webex status |
|
|
277
|
+
| `api.teams` | `TeamsApi` | Teams |
|
|
278
|
+
| `api.team_memberships` | `TeamMembershipsApi` | Team memberships |
|
|
279
|
+
| `api.webhook` | `WebhookApi` | Webhooks |
|
|
280
|
+
| `api.xapi` | `XApi` | xAPI |
|
|
281
|
+
|
|
282
|
+
### Telephony Sub-APIs (via `api.telephony.*`)
|
|
283
|
+
|
|
284
|
+
The `TelephonyApi` object is the gateway to all calling features. Key sub-properties:
|
|
285
|
+
|
|
286
|
+
- `api.telephony.access_codes` -- Location access/authentication codes
|
|
287
|
+
- `api.telephony.announcements_repo` -- Announcements repository
|
|
288
|
+
- `api.telephony.auto_attendant` -- Auto attendant CRUD
|
|
289
|
+
- `api.telephony.call_controls_members` -- Call controls members
|
|
290
|
+
- `api.telephony.call_intercept` -- Location call intercept settings
|
|
291
|
+
- `api.telephony.call_recording` -- Call recording settings
|
|
292
|
+
- `api.telephony.call_routing` -- Call routing
|
|
293
|
+
- `api.telephony.caller_reputation_provider` -- Caller reputation provider
|
|
294
|
+
- `api.telephony.calls` -- Calls API
|
|
295
|
+
- `api.telephony.callpark` -- Call park CRUD
|
|
296
|
+
- `api.telephony.callpark_extension` -- Call park extensions
|
|
297
|
+
- `api.telephony.callqueue` -- Call queue CRUD, agents
|
|
298
|
+
- `api.telephony.conference` -- Conference controls
|
|
299
|
+
- `api.telephony.cx_essentials` -- Customer Assist (formerly CX Essentials)
|
|
300
|
+
- `api.telephony.dect_devices` -- DECT device management
|
|
301
|
+
- `api.telephony.devices` -- Telephony device details, members, MAC validation
|
|
302
|
+
- `api.telephony.emergency_address` -- Emergency address management
|
|
303
|
+
- `api.telephony.emergency_services` -- Org emergency services
|
|
304
|
+
- `api.telephony.guest_calling` -- Guest calling
|
|
305
|
+
- `api.telephony.hotdesk` -- Hot desk management
|
|
306
|
+
- `api.telephony.hotdesking_voiceportal` -- Hot desking sign-in via voice portal
|
|
307
|
+
- `api.telephony.huntgroup` -- Hunt group CRUD
|
|
308
|
+
- `api.telephony.jobs` -- Bulk provisioning jobs
|
|
309
|
+
- `api.telephony.location` -- Location-level telephony settings
|
|
310
|
+
- `api.telephony.locations` -- Alias for `location` (same object)
|
|
311
|
+
- `api.telephony.ms_teams` -- Org MS Teams settings
|
|
312
|
+
- `api.telephony.operating_modes` -- Operating modes
|
|
313
|
+
- `api.telephony.organisation_access_codes` -- Org-level access codes
|
|
314
|
+
- `api.telephony.organisation_voicemail` -- Org voicemail settings
|
|
315
|
+
- `api.telephony.paging` -- Paging groups
|
|
316
|
+
- `api.telephony.permissions_out` -- Outgoing call permissions
|
|
317
|
+
- `api.telephony.phone_numbers(...)` -- List phone numbers org-wide or per-location (method, not sub-API)
|
|
318
|
+
- `api.telephony.pickup` -- Call pickup CRUD
|
|
319
|
+
- `api.telephony.playlist` -- Playlists (music on hold)
|
|
320
|
+
- `api.telephony.pnc` -- Private network connect
|
|
321
|
+
- `api.telephony.prem_pstn` -- Premises PSTN
|
|
322
|
+
- `api.telephony.pstn` -- PSTN settings
|
|
323
|
+
- `api.telephony.schedules` -- Holiday/business hours schedules
|
|
324
|
+
- `api.telephony.supervisors` -- Supervisor management
|
|
325
|
+
- `api.telephony.virtual_extensions` -- Virtual extensions
|
|
326
|
+
- `api.telephony.virtual_lines` -- Virtual line management
|
|
327
|
+
- `api.telephony.voicemail_groups` -- Voicemail groups
|
|
328
|
+
- `api.telephony.voicemail_rules` -- Voicemail rules
|
|
329
|
+
- `api.telephony.voice_messaging` -- Voice messaging
|
|
330
|
+
- `api.telephony.voiceportal` -- Voice portal settings
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 3. Authentication Patterns
|
|
335
|
+
|
|
336
|
+
### Pattern A: Environment Variable (Simplest)
|
|
337
|
+
|
|
338
|
+
Set `WEBEX_ACCESS_TOKEN` and construct with no arguments. Best for quick scripts and developer tokens.
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from dotenv import load_dotenv
|
|
342
|
+
from wxc_sdk import WebexSimpleApi
|
|
343
|
+
|
|
344
|
+
load_dotenv(override=True) # reads .env file
|
|
345
|
+
|
|
346
|
+
api = WebexSimpleApi()
|
|
347
|
+
me = api.people.me()
|
|
348
|
+
print(f'Authenticated as {me.display_name}')
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**.env file:**
|
|
352
|
+
```
|
|
353
|
+
WEBEX_ACCESS_TOKEN=NjY2Yz...
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Pattern B: Token String Directly
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
api = WebexSimpleApi(tokens='NjY2Yz...')
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Pattern C: OAuth Integration (Interactive)
|
|
363
|
+
|
|
364
|
+
For production scripts that need token refresh. Uses `wxc_sdk.integration.Integration` to run a local OAuth flow and cache tokens to a YML file.
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
import os
|
|
368
|
+
from wxc_sdk import WebexSimpleApi
|
|
369
|
+
from wxc_sdk.integration import Integration
|
|
370
|
+
from wxc_sdk.scopes import parse_scopes
|
|
371
|
+
from wxc_sdk.tokens import Tokens
|
|
372
|
+
|
|
373
|
+
def build_integration() -> Integration:
|
|
374
|
+
client_id = os.getenv('TOKEN_INTEGRATION_CLIENT_ID')
|
|
375
|
+
client_secret = os.getenv('TOKEN_INTEGRATION_CLIENT_SECRET')
|
|
376
|
+
scopes = parse_scopes(os.getenv('TOKEN_INTEGRATION_CLIENT_SCOPES'))
|
|
377
|
+
redirect_url = 'http://localhost:6001/redirect'
|
|
378
|
+
return Integration(
|
|
379
|
+
client_id=client_id,
|
|
380
|
+
client_secret=client_secret,
|
|
381
|
+
scopes=scopes,
|
|
382
|
+
redirect_url=redirect_url,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def get_tokens() -> Tokens:
|
|
386
|
+
integration = build_integration()
|
|
387
|
+
# reads from YML cache; if expired, opens browser for OAuth
|
|
388
|
+
tokens = integration.get_cached_tokens_from_yml(yml_path='tokens.yml')
|
|
389
|
+
return tokens
|
|
390
|
+
|
|
391
|
+
tokens = get_tokens()
|
|
392
|
+
api = WebexSimpleApi(tokens=tokens)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Required .env:**
|
|
396
|
+
```
|
|
397
|
+
TOKEN_INTEGRATION_CLIENT_ID=C1234...
|
|
398
|
+
TOKEN_INTEGRATION_CLIENT_SECRET=abc123...
|
|
399
|
+
TOKEN_INTEGRATION_CLIENT_SCOPES="spark-admin:telephony_config_read spark-admin:people_read"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Pattern D: Service App (Headless / Production)
|
|
403
|
+
|
|
404
|
+
Service apps use a refresh token + client credentials to obtain access tokens without user interaction. Best for automation and scheduled jobs.
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
import os
|
|
408
|
+
from wxc_sdk import WebexSimpleApi
|
|
409
|
+
from wxc_sdk.integration import Integration
|
|
410
|
+
from wxc_sdk.tokens import Tokens
|
|
411
|
+
import yaml
|
|
412
|
+
|
|
413
|
+
SERVICE_APP_ENVS = (
|
|
414
|
+
'SERVICE_APP_REFRESH_TOKEN',
|
|
415
|
+
'SERVICE_APP_CLIENT_ID',
|
|
416
|
+
'SERVICE_APP_CLIENT_SECRET',
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def get_tokens() -> Tokens:
|
|
420
|
+
refresh, client_id, client_secret = (
|
|
421
|
+
os.getenv(env) for env in SERVICE_APP_ENVS
|
|
422
|
+
)
|
|
423
|
+
# Try reading cached tokens from YML
|
|
424
|
+
yml_path = f'tokens_{client_id}.yml'
|
|
425
|
+
if os.path.isfile(yml_path):
|
|
426
|
+
with open(yml_path) as f:
|
|
427
|
+
tokens = Tokens.model_validate(yaml.safe_load(f))
|
|
428
|
+
# Refresh if less than 24 hours remaining
|
|
429
|
+
if tokens.remaining >= 24 * 60 * 60:
|
|
430
|
+
return tokens
|
|
431
|
+
|
|
432
|
+
# Get fresh tokens using refresh token
|
|
433
|
+
tokens = Tokens(refresh_token=refresh)
|
|
434
|
+
integration = Integration(
|
|
435
|
+
client_id=client_id,
|
|
436
|
+
client_secret=client_secret,
|
|
437
|
+
scopes=[],
|
|
438
|
+
redirect_url=None,
|
|
439
|
+
)
|
|
440
|
+
integration.refresh(tokens=tokens)
|
|
441
|
+
|
|
442
|
+
# Cache to file
|
|
443
|
+
with open(yml_path, mode='w') as f:
|
|
444
|
+
yaml.safe_dump(tokens.model_dump(exclude_none=True), f)
|
|
445
|
+
return tokens
|
|
446
|
+
|
|
447
|
+
tokens = get_tokens()
|
|
448
|
+
with WebexSimpleApi(tokens=tokens) as api:
|
|
449
|
+
users = list(api.people.list())
|
|
450
|
+
print(f'{len(users)} users')
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Required .env:**
|
|
454
|
+
```
|
|
455
|
+
SERVICE_APP_CLIENT_ID=C5678...
|
|
456
|
+
SERVICE_APP_CLIENT_SECRET=xyz789...
|
|
457
|
+
SERVICE_APP_REFRESH_TOKEN=MmI3Zj...
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Tokens Class Reference
|
|
461
|
+
|
|
462
|
+
`wxc_sdk.tokens.Tokens` (Pydantic BaseModel):
|
|
463
|
+
|
|
464
|
+
| Field | Type | Notes |
|
|
465
|
+
|-------|------|-------|
|
|
466
|
+
| `access_token` | `Optional[str]` | The bearer token |
|
|
467
|
+
| `expires_in` | `Optional[int]` | Lifetime in seconds at creation time |
|
|
468
|
+
| `expires_at` | `Optional[datetime]` | Computed expiration datetime |
|
|
469
|
+
| `refresh_token` | `Optional[str]` | For refresh flow |
|
|
470
|
+
| `refresh_token_expires_in` | `Optional[int]` | Refresh token lifetime |
|
|
471
|
+
| `refresh_token_expires_at` | `Optional[datetime]` | Refresh token expiration |
|
|
472
|
+
| `token_type` | `Optional[Literal['Bearer']]` | Always "Bearer" |
|
|
473
|
+
| `scope` | `Optional[str]` | Space-delimited scopes |
|
|
474
|
+
|
|
475
|
+
Key property: `tokens.remaining` returns seconds until access_token expires.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 4. Sync vs Async
|
|
480
|
+
|
|
481
|
+
The SDK provides parallel sync and async APIs with identical method signatures.
|
|
482
|
+
|
|
483
|
+
| | Sync | Async |
|
|
484
|
+
|---|---|---|
|
|
485
|
+
| **Class** | `WebexSimpleApi` | `AsWebexSimpleApi` |
|
|
486
|
+
| **Import** | `from wxc_sdk import WebexSimpleApi` | `from wxc_sdk.as_api import AsWebexSimpleApi` |
|
|
487
|
+
| **HTTP lib** | `requests` (`RestSession`) | `aiohttp` (`AsRestSession`) |
|
|
488
|
+
| **Context mgr** | `with api:` | `async with api:` |
|
|
489
|
+
| **Concurrency** | `ThreadPoolExecutor` + Semaphore | `asyncio.gather` + Semaphore |
|
|
490
|
+
| **Pagination** | Generator (`yield`) | AsyncGenerator (`async for`) |
|
|
491
|
+
|
|
492
|
+
### When to Use Each
|
|
493
|
+
|
|
494
|
+
**Use sync (`WebexSimpleApi`)** when:
|
|
495
|
+
- Writing simple scripts (list users, read a setting)
|
|
496
|
+
- Your script does a handful of sequential API calls
|
|
497
|
+
- You want the simplest possible code
|
|
498
|
+
|
|
499
|
+
**Use async (`AsWebexSimpleApi`)** when:
|
|
500
|
+
- You need to make many API calls (bulk operations)
|
|
501
|
+
- You want parallel execution without threads
|
|
502
|
+
- Performance matters (async is significantly faster for bulk work)
|
|
503
|
+
|
|
504
|
+
### Async Basic Pattern
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
import asyncio
|
|
508
|
+
from wxc_sdk.as_api import AsWebexSimpleApi
|
|
509
|
+
|
|
510
|
+
async def main():
|
|
511
|
+
async with AsWebexSimpleApi(concurrent_requests=40) as api:
|
|
512
|
+
# list calls are awaitable
|
|
513
|
+
users = [user for user in await api.people.list(calling_data=True)
|
|
514
|
+
if user.location_id]
|
|
515
|
+
|
|
516
|
+
# parallel detail fetches with asyncio.gather
|
|
517
|
+
details = await asyncio.gather(
|
|
518
|
+
*[api.people.details(person_id=u.person_id, calling_data=True)
|
|
519
|
+
for u in users]
|
|
520
|
+
)
|
|
521
|
+
print(f'Got details for {len(details)} users')
|
|
522
|
+
|
|
523
|
+
asyncio.run(main())
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Async Generator Pattern
|
|
527
|
+
|
|
528
|
+
Some async list methods return `AsyncGenerator` via `list_gen()`:
|
|
529
|
+
|
|
530
|
+
```python
|
|
531
|
+
async with AsWebexSimpleApi(concurrent_requests=40) as api:
|
|
532
|
+
calling_license_ids = set(
|
|
533
|
+
lic.license_id for lic in await api.licenses.list()
|
|
534
|
+
if lic.webex_calling
|
|
535
|
+
)
|
|
536
|
+
# list_gen() returns an async generator
|
|
537
|
+
calling_users = [
|
|
538
|
+
user async for user in api.people.list_gen()
|
|
539
|
+
if any(lid in calling_license_ids for lid in user.licenses)
|
|
540
|
+
]
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Side-by-Side Comparison: Sync vs Async
|
|
544
|
+
|
|
545
|
+
**Sync (ThreadPoolExecutor):**
|
|
546
|
+
```python
|
|
547
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
548
|
+
from wxc_sdk import WebexSimpleApi
|
|
549
|
+
|
|
550
|
+
with WebexSimpleApi(concurrent_requests=5) as api:
|
|
551
|
+
locations = [loc for loc in api.locations.list()
|
|
552
|
+
if loc.address.country == 'US']
|
|
553
|
+
with ThreadPoolExecutor() as pool:
|
|
554
|
+
list(pool.map(
|
|
555
|
+
lambda loc: do_something(api=api, location=loc),
|
|
556
|
+
locations
|
|
557
|
+
))
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Async (asyncio.gather):**
|
|
561
|
+
```python
|
|
562
|
+
import asyncio
|
|
563
|
+
from wxc_sdk.as_api import AsWebexSimpleApi
|
|
564
|
+
|
|
565
|
+
async def main():
|
|
566
|
+
async with AsWebexSimpleApi(concurrent_requests=5) as api:
|
|
567
|
+
locations = [loc for loc in await api.locations.list()
|
|
568
|
+
if loc.address.country == 'US']
|
|
569
|
+
await asyncio.gather(
|
|
570
|
+
*[do_something(api=api, location=loc) for loc in locations]
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
asyncio.run(main())
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
## 5. Common Recipes
|
|
579
|
+
|
|
580
|
+
### 5.1 List All Calling-Enabled Users
|
|
581
|
+
|
|
582
|
+
The simplest and most common operation. A user is calling-enabled if `location_id` is set.
|
|
583
|
+
|
|
584
|
+
```python
|
|
585
|
+
from dotenv import load_dotenv
|
|
586
|
+
from wxc_sdk import WebexSimpleApi
|
|
587
|
+
|
|
588
|
+
load_dotenv(override=True)
|
|
589
|
+
api = WebexSimpleApi()
|
|
590
|
+
|
|
591
|
+
calling_users = [user for user in api.people.list(calling_data=True)
|
|
592
|
+
if user.location_id]
|
|
593
|
+
print(f'{len(calling_users)} calling users')
|
|
594
|
+
for user in calling_users:
|
|
595
|
+
print(f' {user.display_name} ({user.emails[0]})')
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Key detail:** `calling_data=True` is required to populate `location_id` and other calling-specific fields. Without it, `location_id` will always be `None`.
|
|
599
|
+
|
|
600
|
+
### 5.2 Filter Users by Location
|
|
601
|
+
|
|
602
|
+
```python
|
|
603
|
+
# Get all locations, find target
|
|
604
|
+
locations = list(api.locations.list())
|
|
605
|
+
target = next(loc for loc in locations if loc.name == 'San Jose')
|
|
606
|
+
|
|
607
|
+
# Get calling users in that location
|
|
608
|
+
calling_users = [user for user in api.people.list(calling_data=True)
|
|
609
|
+
if user.location_id == target.location_id]
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 5.3 Identify Calling Users via License (Async Approach)
|
|
613
|
+
|
|
614
|
+
An alternative to `calling_data=True` that avoids the slower per-user calling data fetch:
|
|
615
|
+
|
|
616
|
+
```python
|
|
617
|
+
async with AsWebexSimpleApi(concurrent_requests=40) as api:
|
|
618
|
+
calling_license_ids = set(
|
|
619
|
+
lic.license_id for lic in await api.licenses.list()
|
|
620
|
+
if lic.webex_calling
|
|
621
|
+
)
|
|
622
|
+
calling_users = [
|
|
623
|
+
user async for user in api.people.list_gen()
|
|
624
|
+
if any(lid in calling_license_ids for lid in user.licenses)
|
|
625
|
+
]
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### 5.4 Bulk Read Person Settings (Async)
|
|
629
|
+
|
|
630
|
+
Reading voicemail, forwarding, or device settings for many users in parallel:
|
|
631
|
+
|
|
632
|
+
```python
|
|
633
|
+
import asyncio
|
|
634
|
+
from wxc_sdk.as_api import AsWebexSimpleApi
|
|
635
|
+
|
|
636
|
+
async def main():
|
|
637
|
+
async with AsWebexSimpleApi(concurrent_requests=40) as api:
|
|
638
|
+
calling_users = [u for u in await api.people.list(calling_data=True)
|
|
639
|
+
if u.location_id]
|
|
640
|
+
|
|
641
|
+
# Get voicemail settings for all users in parallel
|
|
642
|
+
vm_settings = await asyncio.gather(
|
|
643
|
+
*[api.person_settings.voicemail.read(person_id=u.person_id)
|
|
644
|
+
for u in calling_users]
|
|
645
|
+
)
|
|
646
|
+
for user, vm in zip(calling_users, vm_settings):
|
|
647
|
+
print(f'{user.display_name}: rings={vm.send_unanswered_calls.number_of_rings}')
|
|
648
|
+
|
|
649
|
+
asyncio.run(main())
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### 5.5 Bulk Update Person Settings (Sync with ThreadPoolExecutor)
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
656
|
+
from wxc_sdk import WebexSimpleApi
|
|
657
|
+
|
|
658
|
+
api = WebexSimpleApi()
|
|
659
|
+
calling_users = [u for u in api.people.list(calling_data=True) if u.location_id]
|
|
660
|
+
|
|
661
|
+
def set_rings(user):
|
|
662
|
+
vm = api.person_settings.voicemail
|
|
663
|
+
settings = vm.read(person_id=user.person_id)
|
|
664
|
+
settings.send_unanswered_calls.number_of_rings = 6
|
|
665
|
+
vm.configure(user.person_id, settings=settings)
|
|
666
|
+
|
|
667
|
+
with ThreadPoolExecutor() as pool:
|
|
668
|
+
list(pool.map(set_rings, calling_users))
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### 5.6 Reset Call Forwarding for All Users (Async)
|
|
672
|
+
|
|
673
|
+
```python
|
|
674
|
+
import asyncio
|
|
675
|
+
from wxc_sdk.all_types import PersonForwardingSetting
|
|
676
|
+
from wxc_sdk.as_api import AsWebexSimpleApi
|
|
677
|
+
|
|
678
|
+
async def main():
|
|
679
|
+
async with AsWebexSimpleApi() as api:
|
|
680
|
+
calling_users = [u for u in await api.people.list(calling_data=True)
|
|
681
|
+
if u.location_id]
|
|
682
|
+
forwarding = PersonForwardingSetting.default()
|
|
683
|
+
await asyncio.gather(
|
|
684
|
+
*[api.person_settings.forwarding.configure(
|
|
685
|
+
entity_id=u.person_id, forwarding=forwarding)
|
|
686
|
+
for u in calling_users]
|
|
687
|
+
)
|
|
688
|
+
print(f'Reset forwarding for {len(calling_users)} users')
|
|
689
|
+
|
|
690
|
+
asyncio.run(main())
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### 5.7 Call Queue Management
|
|
694
|
+
|
|
695
|
+
**List all queues:**
|
|
696
|
+
```python
|
|
697
|
+
queues = list(api.telephony.callqueue.list())
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Get queue details (includes agents):**
|
|
701
|
+
```python
|
|
702
|
+
details = api.telephony.callqueue.details(
|
|
703
|
+
location_id=queue.location_id,
|
|
704
|
+
queue_id=queue.id
|
|
705
|
+
)
|
|
706
|
+
for agent in details.agents:
|
|
707
|
+
print(f' {agent.first_name} {agent.last_name}: '
|
|
708
|
+
f'{"joined" if agent.join_enabled else "not joined"}')
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Update queue agents:**
|
|
712
|
+
```python
|
|
713
|
+
from wxc_sdk.telephony.callqueue import CallQueue
|
|
714
|
+
from wxc_sdk.telephony.hg_and_cq import Agent
|
|
715
|
+
|
|
716
|
+
# Add an agent
|
|
717
|
+
details.agents.append(Agent(agent_id=user.person_id))
|
|
718
|
+
update = CallQueue(agents=details.agents)
|
|
719
|
+
api.telephony.callqueue.update(
|
|
720
|
+
location_id=queue.location_id,
|
|
721
|
+
queue_id=queue.id,
|
|
722
|
+
update=update,
|
|
723
|
+
)
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**Important:** The `list()` response for queues does NOT include agents. You must call `details()` to get the full agent list.
|
|
727
|
+
|
|
728
|
+
### 5.8 Holiday Schedule Provisioning
|
|
729
|
+
|
|
730
|
+
```python
|
|
731
|
+
from wxc_sdk.all_types import Event, Schedule, ScheduleType
|
|
732
|
+
|
|
733
|
+
# Create a holiday schedule
|
|
734
|
+
schedule = Schedule(
|
|
735
|
+
name='National Holidays',
|
|
736
|
+
schedule_type=ScheduleType.holidays,
|
|
737
|
+
events=[
|
|
738
|
+
Event(
|
|
739
|
+
name='Independence Day 2026',
|
|
740
|
+
start_date=date(2026, 7, 4),
|
|
741
|
+
end_date=date(2026, 7, 4),
|
|
742
|
+
all_day_enabled=True,
|
|
743
|
+
),
|
|
744
|
+
],
|
|
745
|
+
)
|
|
746
|
+
schedule_id = api.telephony.schedules.create(
|
|
747
|
+
obj_id=location.location_id,
|
|
748
|
+
schedule=schedule,
|
|
749
|
+
)
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### 5.9 List Phone Numbers
|
|
753
|
+
|
|
754
|
+
```python
|
|
755
|
+
# All phone numbers in the org
|
|
756
|
+
all_numbers = api.telephony.phone_numbers()
|
|
757
|
+
|
|
758
|
+
# Available (unassigned) numbers in a location
|
|
759
|
+
available = api.telephony.phone_numbers(
|
|
760
|
+
available=True,
|
|
761
|
+
location_id=location.location_id,
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
# Numbers owned by workspaces (places)
|
|
765
|
+
from wxc_sdk.common import OwnerType
|
|
766
|
+
place_numbers = api.telephony.phone_numbers(
|
|
767
|
+
owner_type=OwnerType.place,
|
|
768
|
+
)
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### 5.10 Hunt Group with Call Forwarding
|
|
772
|
+
|
|
773
|
+
```python
|
|
774
|
+
from wxc_sdk.telephony.huntgroup import HuntGroup
|
|
775
|
+
|
|
776
|
+
# Create a hunt group
|
|
777
|
+
settings = HuntGroup.create(
|
|
778
|
+
name='Support HG',
|
|
779
|
+
phone_number='+14085551234',
|
|
780
|
+
)
|
|
781
|
+
hg_id = api.telephony.huntgroup.create(
|
|
782
|
+
location_id=location.location_id,
|
|
783
|
+
settings=settings,
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Set call forwarding on the hunt group
|
|
787
|
+
forwarding = api.telephony.huntgroup.forwarding.settings(
|
|
788
|
+
location_id=location.location_id,
|
|
789
|
+
feature_id=hg_id,
|
|
790
|
+
)
|
|
791
|
+
forwarding.always.enabled = True
|
|
792
|
+
forwarding.always.destination = '+14085559999'
|
|
793
|
+
api.telephony.huntgroup.forwarding.update(
|
|
794
|
+
location_id=location.location_id,
|
|
795
|
+
feature_id=hg_id,
|
|
796
|
+
forwarding=forwarding,
|
|
797
|
+
)
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
### 5.11 Users Without Devices
|
|
801
|
+
|
|
802
|
+
```python
|
|
803
|
+
import asyncio
|
|
804
|
+
from wxc_sdk.as_api import AsWebexSimpleApi
|
|
805
|
+
|
|
806
|
+
async def main():
|
|
807
|
+
async with AsWebexSimpleApi(tokens=tokens) as api:
|
|
808
|
+
calling_users = [u for u in await api.people.list(calling_data=True)
|
|
809
|
+
if u.location_id]
|
|
810
|
+
device_infos = await asyncio.gather(
|
|
811
|
+
*[api.person_settings.devices(person_id=u.person_id)
|
|
812
|
+
for u in calling_users]
|
|
813
|
+
)
|
|
814
|
+
users_wo_devices = [
|
|
815
|
+
u for u, di in zip(calling_users, device_infos)
|
|
816
|
+
if not di.devices
|
|
817
|
+
]
|
|
818
|
+
print(f'{len(users_wo_devices)} users without devices')
|
|
819
|
+
|
|
820
|
+
asyncio.run(main())
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### 5.12 Workspace Provisioning with 3rd-Party Device
|
|
824
|
+
|
|
825
|
+
```python
|
|
826
|
+
from wxc_sdk.workspaces import (
|
|
827
|
+
CallingType, Workspace, WorkspaceCalling,
|
|
828
|
+
WorkspaceSupportedDevices, WorkspaceWebexCalling,
|
|
829
|
+
)
|
|
830
|
+
from wxc_sdk.common import DevicePlatform
|
|
831
|
+
|
|
832
|
+
# Create workspace
|
|
833
|
+
settings = Workspace(
|
|
834
|
+
location_id=location.location_id,
|
|
835
|
+
display_name='Lobby Phone',
|
|
836
|
+
capacity=1,
|
|
837
|
+
supported_devices=WorkspaceSupportedDevices.phones,
|
|
838
|
+
device_platform=DevicePlatform.cisco,
|
|
839
|
+
calling=WorkspaceCalling(
|
|
840
|
+
type=CallingType.webex,
|
|
841
|
+
webex_calling=WorkspaceWebexCalling(
|
|
842
|
+
licenses=[calling_license_id],
|
|
843
|
+
extension='2001',
|
|
844
|
+
location_id=location.location_id,
|
|
845
|
+
),
|
|
846
|
+
),
|
|
847
|
+
)
|
|
848
|
+
workspace = await api.workspaces.create(settings=settings)
|
|
849
|
+
|
|
850
|
+
# Attach a device by MAC address
|
|
851
|
+
device = await api.devices.create_by_mac_address(
|
|
852
|
+
mac='DEADDEAD0001',
|
|
853
|
+
workspace_id=workspace.workspace_id,
|
|
854
|
+
model='Generic IPPhone Customer Managed',
|
|
855
|
+
password='generated_password',
|
|
856
|
+
)
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## 6. Error Handling
|
|
862
|
+
|
|
863
|
+
### RestError (Sync)
|
|
864
|
+
|
|
865
|
+
The sync API raises `wxc_sdk.rest.RestError` (subclass of `requests.HTTPError`) on API errors:
|
|
866
|
+
|
|
867
|
+
```python
|
|
868
|
+
from wxc_sdk.rest import RestError
|
|
869
|
+
|
|
870
|
+
try:
|
|
871
|
+
api.telephony.callqueue.details(
|
|
872
|
+
location_id='bad-id',
|
|
873
|
+
queue_id='bad-id',
|
|
874
|
+
)
|
|
875
|
+
except RestError as e:
|
|
876
|
+
print(f'HTTP {e.response.status_code}')
|
|
877
|
+
print(f'Error code: {e.code}')
|
|
878
|
+
print(f'Description: {e.description}')
|
|
879
|
+
print(f'Tracking ID: {e.detail.tracking_id}')
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
`RestError.detail` is an `ErrorDetail` object with:
|
|
883
|
+
- `message` -- error message
|
|
884
|
+
- `errors` -- list of `SingleError` (each has `description` and `error_code`)
|
|
885
|
+
- `tracking_id` -- Webex tracking ID for support escalation
|
|
886
|
+
|
|
887
|
+
### AsRestError (Async)
|
|
888
|
+
|
|
889
|
+
The async API raises `wxc_sdk.as_rest.AsRestError` (subclass of `aiohttp.ClientResponseError`):
|
|
890
|
+
|
|
891
|
+
```python
|
|
892
|
+
from wxc_sdk.as_rest import AsRestError
|
|
893
|
+
|
|
894
|
+
try:
|
|
895
|
+
await api.telephony.callqueue.details(
|
|
896
|
+
location_id='bad-id',
|
|
897
|
+
queue_id='bad-id',
|
|
898
|
+
)
|
|
899
|
+
except AsRestError as e:
|
|
900
|
+
print(f'HTTP {e.status}')
|
|
901
|
+
print(f'Detail: {e.detail}')
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Handling Errors in Bulk Operations
|
|
905
|
+
|
|
906
|
+
Use `return_exceptions=True` with `asyncio.gather` to collect errors without stopping:
|
|
907
|
+
|
|
908
|
+
```python
|
|
909
|
+
results = await asyncio.gather(
|
|
910
|
+
*[do_something(item) for item in items],
|
|
911
|
+
return_exceptions=True,
|
|
912
|
+
)
|
|
913
|
+
for item, result in zip(items, results):
|
|
914
|
+
if isinstance(result, Exception):
|
|
915
|
+
print(f'Failed on {item}: {result}')
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
## 7. REST Client Details
|
|
921
|
+
|
|
922
|
+
### Base URL
|
|
923
|
+
|
|
924
|
+
All API requests go to `https://webexapis.com/v1`.
|
|
925
|
+
|
|
926
|
+
### Request Headers (Auto-Set)
|
|
927
|
+
|
|
928
|
+
Every request from `RestSession` / `AsRestSession` includes:
|
|
929
|
+
|
|
930
|
+
```
|
|
931
|
+
Authorization: Bearer {access_token}
|
|
932
|
+
Content-Type: application/json;charset=utf-8
|
|
933
|
+
TrackingID: SIMPLE_{uuid4}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Rate Limiting & 429 Retry
|
|
937
|
+
|
|
938
|
+
When `retry_429=True` (the default), the SDK automatically:
|
|
939
|
+
|
|
940
|
+
1. Catches HTTP 429 responses
|
|
941
|
+
2. Reads the `Retry-After` header (defaults to 5 seconds if missing)
|
|
942
|
+
3. Caps the wait at `RETRY_429_MAX_WAIT` (60 seconds, defined in `wxc_sdk.base`)
|
|
943
|
+
4. Sleeps for the specified duration, then retries
|
|
944
|
+
|
|
945
|
+
The retry loop runs inside a Semaphore-protected block, so the concurrency limit is respected.
|
|
946
|
+
|
|
947
|
+
**Sync:** Uses `time.sleep(retry_after)`
|
|
948
|
+
**Async:** Uses `await asyncio.sleep(retry_after)`
|
|
949
|
+
|
|
950
|
+
### Concurrency Control
|
|
951
|
+
|
|
952
|
+
Both sync and async sessions use a Semaphore to limit concurrent requests:
|
|
953
|
+
|
|
954
|
+
```python
|
|
955
|
+
# Sync: threading.Semaphore
|
|
956
|
+
# Async: asyncio.Semaphore
|
|
957
|
+
self._sem = Semaphore(concurrent_requests)
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
Every request acquires the semaphore before execution. This prevents overwhelming the Webex API. The `concurrent_requests` parameter controls the limit (default: 10).
|
|
961
|
+
|
|
962
|
+
For bulk async operations, you can raise this significantly:
|
|
963
|
+
|
|
964
|
+
```python
|
|
965
|
+
async with AsWebexSimpleApi(concurrent_requests=40) as api:
|
|
966
|
+
...
|
|
967
|
+
|
|
968
|
+
# Some examples go as high as 100:
|
|
969
|
+
async with AsWebexSimpleApi(tokens=tokens, concurrent_requests=100) as api:
|
|
970
|
+
...
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
### Pagination (Automatic)
|
|
974
|
+
|
|
975
|
+
The SDK handles RFC 5988 pagination transparently via `follow_pagination()`. List methods return generators (sync) or async generators that automatically fetch subsequent pages by following the `next` link header.
|
|
976
|
+
|
|
977
|
+
You never need to handle pagination manually. Just iterate:
|
|
978
|
+
|
|
979
|
+
```python
|
|
980
|
+
# This fetches ALL pages automatically
|
|
981
|
+
all_users = list(api.people.list(calling_data=True))
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
Internally, `follow_pagination()`:
|
|
985
|
+
1. Makes the initial GET request
|
|
986
|
+
2. Parses `response.links['next']['url']` for the next page
|
|
987
|
+
3. Yields each item from the `items` key (or the first list-valued key in the response)
|
|
988
|
+
4. Repeats until no `next` link is present
|
|
989
|
+
|
|
990
|
+
### Proxy and SSL Support
|
|
991
|
+
|
|
992
|
+
```python
|
|
993
|
+
# Sync
|
|
994
|
+
api = WebexSimpleApi(
|
|
995
|
+
tokens=tokens,
|
|
996
|
+
proxy_url='https://proxy.corp.com:8080',
|
|
997
|
+
verify='/path/to/ca-bundle.crt', # or False to disable
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
# Async
|
|
1001
|
+
api = AsWebexSimpleApi(
|
|
1002
|
+
tokens=tokens,
|
|
1003
|
+
proxy_url='https://proxy.corp.com:8080',
|
|
1004
|
+
ssl=ssl_context, # ssl.SSLContext or bool
|
|
1005
|
+
)
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### Response Callbacks
|
|
1009
|
+
|
|
1010
|
+
You can register callbacks to inspect every HTTP response (useful for logging, HAR capture, metrics):
|
|
1011
|
+
|
|
1012
|
+
```python
|
|
1013
|
+
def my_callback(response, diff_ns):
|
|
1014
|
+
print(f'{response.request.method} {response.request.url} '
|
|
1015
|
+
f'took {diff_ns / 1e6:.1f}ms')
|
|
1016
|
+
|
|
1017
|
+
callback_id = api.session.register_response_callback(my_callback)
|
|
1018
|
+
# ... do work ...
|
|
1019
|
+
api.session.unregister_response_callback(callback_id)
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
The SDK automatically registers a debug-level logging callback that dumps full request/response details when `logging.DEBUG` is enabled.
|
|
1023
|
+
|
|
1024
|
+
### HAR Writer
|
|
1025
|
+
|
|
1026
|
+
Several examples use `wxc_sdk.har_writer.HarWriter` for saving HTTP traces in HAR format:
|
|
1027
|
+
|
|
1028
|
+
```python
|
|
1029
|
+
from wxc_sdk.har_writer import HarWriter
|
|
1030
|
+
|
|
1031
|
+
async with AsWebexSimpleApi(tokens=tokens) as api:
|
|
1032
|
+
with HarWriter(api=api, path='trace.har'):
|
|
1033
|
+
# all requests during this block are captured
|
|
1034
|
+
await api.people.list()
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
## 8. Data Types
|
|
1040
|
+
|
|
1041
|
+
### Import Patterns
|
|
1042
|
+
|
|
1043
|
+
The SDK provides a convenience module `wxc_sdk.all_types` that re-exports all model types:
|
|
1044
|
+
|
|
1045
|
+
```python
|
|
1046
|
+
# Import everything (convenient for interactive/scripts)
|
|
1047
|
+
from wxc_sdk.all_types import *
|
|
1048
|
+
|
|
1049
|
+
# Import specific types (preferred for production code)
|
|
1050
|
+
from wxc_sdk.all_types import PersonForwardingSetting, Schedule, ScheduleType, Event
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
For types not in `all_types`, import from the specific module:
|
|
1054
|
+
|
|
1055
|
+
```python
|
|
1056
|
+
from wxc_sdk.people import Person
|
|
1057
|
+
from wxc_sdk.telephony.callqueue import CallQueue
|
|
1058
|
+
from wxc_sdk.telephony.hg_and_cq import Agent
|
|
1059
|
+
from wxc_sdk.telephony.huntgroup import HuntGroup
|
|
1060
|
+
from wxc_sdk.telephony.forwarding import CallForwarding
|
|
1061
|
+
from wxc_sdk.telephony import NumberListPhoneNumber
|
|
1062
|
+
from wxc_sdk.telephony.location import TelephonyLocation
|
|
1063
|
+
from wxc_sdk.telephony.devices import DeviceMember, DeviceMembersResponse
|
|
1064
|
+
from wxc_sdk.common import UserType, OwnerType, AlternateNumber, RingPattern, IdAndName
|
|
1065
|
+
from wxc_sdk.locations import Location
|
|
1066
|
+
from wxc_sdk.licenses import License
|
|
1067
|
+
from wxc_sdk.workspaces import Workspace, CallingType, WorkspaceCalling
|
|
1068
|
+
from wxc_sdk.person_settings import TelephonyDevice, DeviceList
|
|
1069
|
+
from wxc_sdk.person_settings.permissions_out import Action, DigitPattern
|
|
1070
|
+
from wxc_sdk.authorizations import Authorization, AuthorizationType
|
|
1071
|
+
from wxc_sdk.tokens import Tokens
|
|
1072
|
+
from wxc_sdk.integration import Integration
|
|
1073
|
+
from wxc_sdk.scopes import parse_scopes
|
|
1074
|
+
from wxc_sdk.webhook import WebhookEvent, WebhookEventType
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
### ApiModel Base Class
|
|
1078
|
+
|
|
1079
|
+
All data models inherit from `wxc_sdk.base.ApiModel` (which extends `pydantic.BaseModel`):
|
|
1080
|
+
|
|
1081
|
+
- **Alias generation:** Python `snake_case` attributes are automatically mapped to `camelCase` JSON keys via `to_camel`.
|
|
1082
|
+
- **Populate by name:** Both `snake_case` and `camelCase` field names are accepted.
|
|
1083
|
+
- **Extra fields allowed:** By default, unknown JSON fields are kept (configurable via `API_MODEL_ALLOW_EXTRA` env var).
|
|
1084
|
+
- **Enum values stored:** Enum fields store the value, not the enum member.
|
|
1085
|
+
- **Serialization defaults:** `model_dump_json()` excludes `None` values and uses aliases by default.
|
|
1086
|
+
|
|
1087
|
+
### SafeEnum
|
|
1088
|
+
|
|
1089
|
+
`wxc_sdk.base.SafeEnum` extends `aenum.Enum` to automatically handle unknown enum values from the API. If the API returns a value not defined in the enum, it is dynamically added (with a warning log) rather than raising an error. This prevents breakage when Webex adds new enum values.
|
|
1090
|
+
|
|
1091
|
+
### Utility Functions
|
|
1092
|
+
|
|
1093
|
+
```python
|
|
1094
|
+
from wxc_sdk.base import webex_id_to_uuid, dt_iso_str, plus1
|
|
1095
|
+
|
|
1096
|
+
# Convert a Webex base64-encoded ID to a UUID
|
|
1097
|
+
uuid = webex_id_to_uuid('Y2lzY29zcGFyazovL3VzL...')
|
|
1098
|
+
|
|
1099
|
+
# Format datetime for Webex API (UTC, milliseconds)
|
|
1100
|
+
from datetime import datetime
|
|
1101
|
+
iso = dt_iso_str(datetime.now()) # '2026-03-16T12:00:00.000Z'
|
|
1102
|
+
|
|
1103
|
+
# Convert 10-digit number to +E.164
|
|
1104
|
+
phone = plus1('4085551234') # '+14085551234'
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
---
|
|
1108
|
+
|
|
1109
|
+
## 9. Environment Variable Patterns
|
|
1110
|
+
|
|
1111
|
+
### Recommended .env Structure
|
|
1112
|
+
|
|
1113
|
+
Most examples follow this pattern: a `.env` file named after the script.
|
|
1114
|
+
|
|
1115
|
+
```
|
|
1116
|
+
# For simple scripts: .env
|
|
1117
|
+
WEBEX_ACCESS_TOKEN=NjY2Yz...
|
|
1118
|
+
|
|
1119
|
+
# For OAuth integration: get_tokens.env
|
|
1120
|
+
TOKEN_INTEGRATION_CLIENT_ID=C1234...
|
|
1121
|
+
TOKEN_INTEGRATION_CLIENT_SECRET=abc123...
|
|
1122
|
+
TOKEN_INTEGRATION_CLIENT_SCOPES="spark-admin:telephony_config_read"
|
|
1123
|
+
|
|
1124
|
+
# For service apps: service_app.env
|
|
1125
|
+
SERVICE_APP_CLIENT_ID=C5678...
|
|
1126
|
+
SERVICE_APP_CLIENT_SECRET=xyz789...
|
|
1127
|
+
SERVICE_APP_REFRESH_TOKEN=MmI3Zj...
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
### Token Caching Pattern
|
|
1131
|
+
|
|
1132
|
+
All OAuth/service-app examples cache tokens to a YML file and check `tokens.remaining` before use:
|
|
1133
|
+
|
|
1134
|
+
```python
|
|
1135
|
+
import yaml
|
|
1136
|
+
from wxc_sdk.tokens import Tokens
|
|
1137
|
+
|
|
1138
|
+
def read_tokens(path: str) -> Tokens:
|
|
1139
|
+
with open(path) as f:
|
|
1140
|
+
return Tokens.model_validate(yaml.safe_load(f))
|
|
1141
|
+
|
|
1142
|
+
def write_tokens(path: str, tokens: Tokens):
|
|
1143
|
+
with open(path, mode='w') as f:
|
|
1144
|
+
yaml.safe_dump(tokens.model_dump(exclude_none=True), f)
|
|
1145
|
+
|
|
1146
|
+
# Refresh if less than 24 hours remaining
|
|
1147
|
+
if tokens.remaining < 24 * 60 * 60:
|
|
1148
|
+
tokens = refresh_tokens()
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
---
|
|
1152
|
+
|
|
1153
|
+
## 10. Logging
|
|
1154
|
+
|
|
1155
|
+
The SDK uses Python's standard `logging` module. Key loggers:
|
|
1156
|
+
|
|
1157
|
+
| Logger | Purpose |
|
|
1158
|
+
|--------|---------|
|
|
1159
|
+
| `wxc_sdk.rest` | Sync REST request/response details |
|
|
1160
|
+
| `wxc_sdk.as_rest` | Async REST request/response details |
|
|
1161
|
+
| `wxc_sdk` | General SDK messages |
|
|
1162
|
+
|
|
1163
|
+
### Enable Full Request Logging
|
|
1164
|
+
|
|
1165
|
+
```python
|
|
1166
|
+
import logging
|
|
1167
|
+
|
|
1168
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
1169
|
+
# Optional: reduce noise from urllib3
|
|
1170
|
+
logging.getLogger('urllib3').setLevel(logging.INFO)
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### Log REST to File Only
|
|
1174
|
+
|
|
1175
|
+
```python
|
|
1176
|
+
import logging
|
|
1177
|
+
|
|
1178
|
+
# Console: INFO only
|
|
1179
|
+
logging.basicConfig(level=logging.INFO)
|
|
1180
|
+
|
|
1181
|
+
# REST details to file at DEBUG
|
|
1182
|
+
rest_logger = logging.getLogger('wxc_sdk.as_rest')
|
|
1183
|
+
rest_logger.setLevel(logging.DEBUG)
|
|
1184
|
+
handler = logging.FileHandler('rest.log', mode='w')
|
|
1185
|
+
handler.setLevel(logging.DEBUG)
|
|
1186
|
+
rest_logger.addHandler(handler)
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
The SDK's response dump (enabled at DEBUG level) includes:
|
|
1190
|
+
- Request method, URL, status code, timing
|
|
1191
|
+
- Request headers (Authorization masked as `Bearer ***`)
|
|
1192
|
+
- Request body (form data secrets masked)
|
|
1193
|
+
- Response headers and body
|
|
1194
|
+
- Access/refresh tokens in response bodies are masked as `***`
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
## 11. Key Gotchas
|
|
1199
|
+
|
|
1200
|
+
1. **`calling_data=True` is required** -- `api.people.list()` without `calling_data=True` will not populate `location_id`, phone numbers, or other calling-specific fields.
|
|
1201
|
+
|
|
1202
|
+
2. **`list()` returns minimal data** -- Queue, hunt group, and auto attendant list responses omit details like agents and alternate numbers. Always call `details()` to get the full object.
|
|
1203
|
+
|
|
1204
|
+
3. **`concurrent_requests` default is 10** -- This is conservative. For bulk async operations, increase to 40-100.
|
|
1205
|
+
|
|
1206
|
+
4. **Async list methods may differ** -- Some APIs have both `.list()` (returns a full list) and `.list_gen()` (returns an async generator). Check which is available for your use case.
|
|
1207
|
+
|
|
1208
|
+
5. **`MAX_USERS_WITH_CALLING_DATA = 10`** -- The SDK internally limits concurrent requests for users with calling data to prevent API issues. This is hardcoded in `as_api.py`.
|
|
1209
|
+
|
|
1210
|
+
6. **Thread safety (sync)** -- The sync `RestSession` uses a `threading.Semaphore` so it is safe to share across threads. Use `ThreadPoolExecutor` for parallel work with the sync API.
|
|
1211
|
+
|
|
1212
|
+
7. **Service app tokens expire** -- Always check `tokens.remaining` and refresh proactively. The examples use a 24-hour threshold.
|
|
1213
|
+
|
|
1214
|
+
8. **Webex IDs are base64-encoded** -- Use `webex_id_to_uuid()` if you need the raw UUID (e.g., for matching against other systems).
|
|
1215
|
+
|
|
1216
|
+
9. **Agent model imports differ per feature** -- Hunt Groups and Call Queues use `from wxc_sdk.telephony.hg_and_cq import Agent`. Call Park and Call Pickup use `from wxc_sdk.common import PersonPlaceAgent`. Paging uses `from wxc_sdk.telephony.paging import PagingAgent`. Do NOT try importing Agent from the individual feature modules.
|
|
1217
|
+
|
|
1218
|
+
10. **Named delete methods vs generic `.delete()`** -- Call Park, Call Pickup, Paging, and several other APIs inherit a generic `.delete()` that accepts anything silently. Always use the named method: `delete_callpark()`, `delete_pickup()`, `delete_paging()`, `delete_huntgroup()`, `delete_queue()`, `delete_auto_attendant()`, `delete_schedule()`.
|
|
1219
|
+
|
|
1220
|
+
11. **`ScheduleApi` import path** -- Lives at `wxc_sdk.common.schedules`, NOT `wxc_sdk.telephony.schedules` (which doesn't exist). `from wxc_sdk.common.schedules import ScheduleApi, Schedule, Event, ScheduleType`.
|
|
1221
|
+
|
|
1222
|
+
12. **`PagingApi.update()` unusual parameter order** -- Signature is `(location_id, update: Paging, paging_id)` — the model object comes BEFORE the ID. Always use keyword arguments to avoid silent misassignment.
|
|
1223
|
+
|
|
1224
|
+
---
|
|
1225
|
+
|
|
1226
|
+
## See Also
|
|
1227
|
+
|
|
1228
|
+
- **`authentication.md`** — Detailed OAuth flows, scope reference, and common auth error handling.
|
|
1229
|
+
- **`provisioning.md`** — End-to-end user provisioning workflows, license assignment (People API and PATCH methods), and location management.
|