--- name: azure-connectornamespace-aca-sandbox description: | Azure Connector Namespace — ACA sandbox edition. Manage connector namespaces, connections, and triggers that wire external services (Office 365, Teams, Forms, SharePoint, OneDrive, GitHub, Azure Blob) into ACA sandbox apps via event-driven triggers or direct API calls using connection runtime URLs. Use when: - Managing connector namespaces and connections for ACA sandboxes - Creating trigger configs whose callbacks target sandbox endpoints - Wiring event sources to ACA sandbox callbacks via gatewayConnections - Building sandbox apps that call connector APIs (send email, post Teams message, etc.) - Reacting to events from one service and calling another Triggers: "create trigger", "trigger config", "webhook trigger", "connector namespace", "connector", "connection", "email trigger", "send email", "onedrive", "sharepoint", "teams", "teams message", "microsoft forms", "form response", "aca sandbox", "sandbox group", "container apps sandbox", "notify", "automate", "when", "on new" --- # Azure Connector Namespace — ACA sandbox edition Manage connectors, connections, and triggers — connect external services to sandbox apps via direct API calls or event-driven triggers. ## Common scenarios | User request | Pattern | Connectors involved | |---|---|---| | "Send a Teams message when a form is submitted" | Trigger (Forms) + Direct API (Teams) | `microsoftforms`, `teams` | | "Notify a channel when a file is uploaded" | Trigger (OneDrive/Blob) + Direct API (Teams) | `onedriveforbusiness`/`azureblob`, `teams` | | "Send an email when a SharePoint list item changes" | Trigger (SharePoint) + Direct API (Office 365) | `sharepointonline`, `office365` | | "Post to Teams when a new email arrives" | Trigger (Office 365) + Direct API (Teams) | `office365`, `teams` | | "Save form responses to SharePoint" | Trigger (Forms) + Direct API (SharePoint) | `microsoftforms`, `sharepointonline` | ## Rules (MUST follow) | Rule | Details | |------|---------| | **No hallucination** | Check `references/` for details. Use `az rest --help` for syntax. | | **No generated notebooks/scripts** | Do NOT generate a notebook or standalone script for the user. Walk through interactively. (Reference scripts in `scripts/` and `labs/` exist for learning.) | | **No MCP configs** | Sandbox apps call runtime URL directly via HTTP. If you reach `mcp-config create`, STOP. | | **No guessing dynamic values** | `x-ms-dynamic-*` → call API, present results, STOP. Never assume a team/channel/folder/site. | | **Execute, don't ask** | Gather inputs → execute immediately → report. Never say "Can I run this?" | | **No az extensions** | Gateway = `az rest`. Sandbox = `aca` CLI. Do NOT use `az connectorgateway/sandbox/sandboxgroup`. | | **Always `@$tmpFile`** | For `az rest --body` in PowerShell — inline JSON breaks. Bash examples in references/ use inline for brevity (shell quoting works in bash). See [gotchas.md](references/gotchas.md). | | **Trigger body schema** | Uses `metadata` + `notificationDetails` (callbackUrl/body/auth). `operationName`+`parameters` at properties root. `callbackTarget` does NOT exist. See Step 5B template. | | **Handler deploy** | Write to local file → `aca sandbox fs write`. Never inline Python in PowerShell. | | **SSL/stderr** | `REQUESTS_CA_BUNDLE` preferred. `verify=False` needs `disable_warnings()`. stderr = trigger failure. See [handler-guide.md](references/handler-guide.md). | | **Parallel execution** | Run independent ops (connections, ACLs, gatewayConnections wiring, dynamic values) as parallel tool calls. | | **Sandbox ↔ connection wiring** | Use the declarative **gatewayConnections** pattern (SG-level PATCH + per-sandbox PUT body). See [gateway-connections.md](references/gateway-connections.md). | | **Swagger / operation discovery** | Always fetch the connector Swagger via the connection's runtime metadata URL — see [swagger-discovery.md](references/swagger-discovery.md) for the full pattern (auth, ACL idempotency, parsing, picking the right operation). | | **ACL idempotency** | When creating a **user-ACL** on a connection (for swagger discovery from outside a sandbox), ALWAYS GET the policy first and only PUT if it doesn't exist. Never blindly recreate a user-ACL. (This does not apply to `gateway-acl` / `sandbox-acl` — those are typically PUT directly during setup.) | | **Narrate progress** | The user must know what's happening at each step **before** you run the shell. Print a short chat message (NOT inside the shell block) with: (a) what you're doing in one line, (b) the exact URL or resource ID being touched, and (c) why. Never run a shell command silently or with only a collapsed shell block. | **When to STOP and ask the user:** Any parameter with dynamic values (teams, channels, folders, sites, lists), choosing integration pattern, OAuth consent. **You must NEVER skip this — always fetch the list and present it.** **When to EXECUTE immediately:** creating gateways/connections/triggers/policies, wiring gatewayConnections, deploying handlers, installing deps. ### Step 0: Prerequisites & Azure context 1. Check `az account show` and `aca --version`. If missing, see [prerequisites.md](references/prerequisites.md). 2. **Select subscription** — list available subscriptions, ask the user to pick: ```bash az account list --query "[].{name:name, id:id, isDefault:isDefault}" -o table ``` Present the list and ask: "Which subscription do you want to use?" If the user picks a non-default subscription, set it: ```bash az account set --subscription "{selected_subscription_id}" ``` Store the subscription ID for all subsequent commands. 3. **Select resource group** — ask: "Do you have an existing resource group, or should I create one?" - If **existing**: list and let user pick: ```bash az group list --query "[].{name:name, location:location}" -o table ``` - If **new**: ask for name + location, then create: ```bash az group create --name {rg} --location {location} ``` **Stop and wait for the user's answers before continuing.** ### Step 1: Understand the scenario Ask the user: - "What event do you want to trigger on?" (new email, SharePoint list item, file upload, etc.) - Map the answer to a connector using this table: | User says | Connector name | Common triggers | |-----------|---------------|-----------------| | Email, Outlook | `office365` | `OnNewEmailV3`, `OnFlaggedEmail` | | SharePoint, list | `sharepointonline` | `OnNewItem`, `OnUpdatedItem` | | OneDrive, files | `onedriveforbusiness` | `OnNewFile`, `OnUpdatedFile` | | Teams | `teams` | `OnNewChannelMessage` | | Forms, survey, quiz | `microsoftforms` | `OnFormResponse`, `OnNewResponse` | | Azure Blob | `azureblob` | `OnNewBlob`, `OnUpdatedBlob` | - Ask if they already know the trigger operation, or want to discover available ones. **Stop and wait for the user's answer before continuing.** ### Step 2: Gateway setup > **⚡ Parallel batch:** Once you know the gateway name, run ALL of these in one parallel call: > 1. Get gateway info (principalId, tenantId, location) > 2. List existing connections (names, statuses, runtime URLs) > 3. Get sandbox group identity (if sandbox already exists) > > This avoids sequential round-trips and saves ~2 minutes. > **ARM base URL:** `https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways` > **API version:** `api-version=2026-05-01-preview` > Use the subscription and resource group selected in Step 0. Ask the user: - "Do you have an existing connector, or should I create a new one?" - If **existing**: ask for the gateway name, then retrieve it (using sub/rg from Step 0): ```bash az rest --method GET \ --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" \ --query "{name:name, principalId:identity.principalId, tenantId:identity.tenantId}" ``` - If **new**: ask for gateway name + location, then **create it immediately** with a SystemAssigned managed identity (required for trigger callbacks): ```powershell $gwBody = @{ location = "{location}"; identity = @{ type = "SystemAssigned" } } | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $gwBody az rest --method PUT ` --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}?api-version=2026-05-01-preview" ` --body "@$tmp" ` --query "{name:name, principalId:identity.principalId, tenantId:identity.tenantId}" Remove-Item $tmp ``` - **Always** capture `principalId` and `tenantId` — they are needed later for access policies and InvokePort auth. - List existing connections: ```bash az rest --method GET \ --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections?api-version=2026-05-01-preview" \ --query "value[].{name:name, status:properties.statuses[0].status, connector:properties.connectorName}" ``` **Once you have the gateway info, proceed immediately to Step 3.** ### Step 3: Create connection(s) + authenticate Create ALL needed connections in parallel, then consent all at once: ```powershell # Create connections (parallel tool calls if multiple): $connBody = @{ properties = @{ connectorName = "office365" }; location = "{location}" } | ConvertTo-Json -Compress $tmp = New-TemporaryFile; Set-Content $tmp $connBody az rest --method PUT ` --url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways/{gw}/connections/o365-conn?api-version=2026-05-01-preview" ` --body "@$tmp" Remove-Item $tmp # Repeat for additional connections (onedriveforbusiness, sharepointonline, etc.) ``` Generate consent links and open in browser. → **Exact format:** See [consent.md](references/consent.md) > **⚠️ Use `Start-Process` to open links. Body MUST use `parameters` array with > `objectId`/`tenantId` from the connection. Do NOT try other formats or print the URL.** Ask user to authenticate (use `ask_user`), then verify: ```bash az rest --method GET \ --url ".../{gw}/connections?api-version=2026-05-01-preview" \ --query "value[].{name:name, status:properties.statuses[0].status}" # All should show: Connected. If not, re-consent. ``` ### Step 4: Choose integration pattern Ask the user: - **A) Direct API calls** — call connector operations on demand via `dynamicInvoke` (send email, read list, create file). If deploying to sandbox, also wires the connection to the sandbox group + sandbox via `gatewayConnections[]`. - **B) Event-driven triggers** — gateway pushes notifications to sandbox when events happen. Handler can then use direct API calls for additional actions. **Stop and wait for the user's answer.** - If **A** → **Step 5A** - If **B** → **Step 5B** --- ### Step 5A: Direct API calls via dynamicInvoke → **Full details:** [direct-api.md](references/direct-api.md) | **Swagger discovery:** [swagger-discovery.md](references/swagger-discovery.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md) 1. Fetch the connector Swagger and pick an operation — see [swagger-discovery.md](references/swagger-discovery.md). 2. Call `dynamicInvoke` on the connection with the resolved `method` + `path` 3. If parameters have `x-ms-dynamic-*` → resolve via dynamicInvoke, show display names to user, store values **If deploying to sandbox:** Wire the connection declaratively (sandbox-group `gatewayConnections[]` PATCH + per-sandbox PUT body referencing the same connection) → [gateway-connections.md](references/gateway-connections.md). The platform proxy injects Bearer auth on every runtime-URL call automatically; the handler makes plain `requests.get(...)` calls with no auth header. **→ Skip to Final verification checklist.** --- ### Step 5B: Event-driven triggers → **Full trigger setup (Steps 5B–9B):** [trigger-setup.md](references/trigger-setup.md) | **Swagger discovery:** [swagger-discovery.md](references/swagger-discovery.md) | **Dynamic values:** [dynamic-values.md](references/dynamic-values.md) 1. Discover trigger operations — fetch the Swagger and filter paths whose operation has `x-ms-trigger` set. See [swagger-discovery.md](references/swagger-discovery.md). 2. If trigger type is `batch` (polling): inform user it polls every ~3 minutes by default. Ask if they want a different interval. 3. Collect parameters (resolve `x-ms-dynamic-*` via Swagger + dynamicInvoke) 4. Ask user: sandbox (existing/new) + callback type (ShellCommand / ExecuteCommand / InvokePort) 5. Create trigger + access policy + role assignment (**run in parallel**) — canonical template in [trigger-setup.md](references/trigger-setup.md) Step 8B 6. Verify trigger state is `Enabled` > **⚠️ Do NOT use `callbackTarget`** — that field does not exist. Correct schema: `metadata` + `notificationDetails`. See [trigger-setup.md](references/trigger-setup.md). | Target | Callback URL | Notes | |--------|-------------|-------| | ShellCommand | `https://management.{region}.azuredevcompute.io/.../executeShellCommand` | Auto-resumes sandbox; needs RBAC `c24cf47c-...` on sandbox group. **Regional host required** (unregional → 404 GlobalSandboxNotFound). | | ExecuteCommand | `https://management.{region}.azuredevcompute.io/.../executeCommand` | Same as above, no shell interpretation. **Regional host required.** | | InvokePort | `https://{id}--{port}.proxy.azuredevcompute.io/...` | Sandbox must be running; needs port auth | After trigger creation → deploy handler. See [handler-guide.md](references/handler-guide.md). --- ### Final verification checklist **For Direct API calls (path A):** - ✅ Gateway exists, connection `Connected`, `connectionRuntimeUrl` available - ✅ Access policies on connection: `gateway-acl` (gateway MI) AND `sandbox-acl` (sandbox-group MI) - ✅ Sandbox group has SystemAssigned MI (`aca sandboxgroup identity assign --name {sg} --system-assigned` if `principalId` is null) - ✅ Sandbox-group `properties.gatewayConnections[]` includes an entry with this connection's `{resourceId, connectionRuntimeUrl, authentication.type=SystemAssignedManagedIdentity}` (PATCH; merge-don't-clobber) - ✅ Each sandbox was created with `gatewayConnections: [{resourceId}]` in its data-plane PUT body (the aca CLI doesn't expose `--gateway-connection`; use `az rest` data-plane PUT) - ✅ Test call from sandbox works (no auth header needed — platform proxy injects Bearer) **For Event-driven triggers (path B):** - ✅ Gateway has SystemAssigned identity, connection `Connected` - ✅ Trigger state is `Enabled`, `gateway-acl` exists (gateway MI → connection) - ✅ RBAC role on sandbox group (ShellCommand) OR port auth (InvokePort) - ✅ If handler calls runtime URL: also needs `sandbox-acl` + sandbox-group `gatewayConnections[]` entry + per-sandbox `gatewayConnections[{resourceId}]` (same as path A) After setup → deploy the handler app. See [handler-guide.md](references/handler-guide.md). ## Quick reference ```bash # ARM base: https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/connectorGateways # API version: api-version=2026-05-01-preview # Gateway az rest --method GET --url ".../connectorGateways/{gw}?api-version=2026-05-01-preview" # Connections az rest --method GET --url ".../connectorGateways/{gw}/connections?api-version=2026-05-01-preview" # Connector Swagger (paths, parameters, x-ms-dynamic-* extensions) — use to list operations or pick a specific one. # See references/swagger-discovery.md for the full fetch pattern (metadata URL + user-ACL + API Hub token): # az rest --method GET --url "?export=true" --resource "https://apihub.azure.com" # Dynamic invoke az rest --method POST --url ".../connectorGateways/{gw}/connections/{conn}/dynamicInvoke?api-version=2026-05-01-preview" --body '{"request":{"method":"GET","path":"/..."}}' # Trigger configs az rest --method GET --url ".../connectorGateways/{gw}/triggerConfigs?api-version=2026-05-01-preview" az rest --method GET --url ".../connectorGateways/{gw}/triggerConfigs/{name}?api-version=2026-05-01-preview" --query "properties.state" ``` ## References - [direct-api.md](references/direct-api.md) — Full dynamicInvoke details, parameter resolution, examples - [consent.md](references/consent.md) — OAuth consent link generation (exact body format) - [trigger-setup.md](references/trigger-setup.md) — Full trigger creation commands (Steps 5B–9B) - [handler-guide.md](references/handler-guide.md) — Handler development, event delivery, templates - [dynamic-values.md](references/dynamic-values.md) — Dynamic parameter resolution algorithms - [gateway-connections.md](references/gateway-connections.md) — Sandbox ↔ connection wiring (declarative gatewayConnections[]) - [runtime-url-examples.md](references/runtime-url-examples.md) — Curl examples for all connectors - [gotchas.md](references/gotchas.md) — Common issues and solutions - [trigger-flow.md](references/trigger-flow.md) — Trigger architecture details - [prerequisites.md](references/prerequisites.md) — Setup requirements - [quickstart.md](references/quickstart.md) — Quick start guide - [tutorial-welcome-emailer.md](references/tutorial-welcome-emailer.md) — Pattern A walkthrough: deploy a welcome emailer to a sandbox