This guide selects an existing Microsoft Foundry Project or creates one in an empty Azure subscription. It is the shared source of truth for subscription, Project endpoint, permissions, model mode, and region eligibility. Do not continue to an example until those values are verified.
After completing the criteria below, continue with 02: MCP settings, deployment, and development.
- The selected Azure subscription is correct and enabled.
- The Voice Agent team has confirmed the subscription and region are eligible.
Microsoft.CognitiveServicesreportsRegistered.- The resource group, AIServices account, and Project report
Succeeded. - The deployment user has both management-plane and Agent data-plane access.
- The Project managed identity has Agent data-plane access.
PROJECT_ENDPOINT,PROJECT_ID, andFOUNDRY_SCOPEare populated.- The Finance definitions'
model_typeand exactmodelidentifier match a mode available to the Project.
Voice Agent availability is region- and subscription-gated. Ask the Voice Agent
service owner to confirm both before creating resources. Do not copy a region
from this repository: the current repository overview names swedencentral
and francecentral, but availability can change and customer subscriptions can
have different enablement.
Do not silently substitute a normal prompt Agent or change to self-deployed
mode when kind: voice or the requested managed realtime model is unavailable.
The two Finance definitions default to this versioned service-managed Voice Agent model:
"model_type": "managed",
"model": "gpt-realtime-2.1"This does not reference a customer-created account deployment. Managed model
identifiers vary by Project. Start with gpt-realtime-2.1. If publication
reports it unsupported, explicitly set VOICE_AGENT_MODEL=gpt-realtime-1.5
and retry. If neither is enabled, use another exact managed identifier
confirmed for that Project, such as gpt-realtime-2.1-mini; do not use the
unversioned gpt-realtime alias as a compatibility fallback.
Set the same VOICE_AGENT_MODEL value in both Finance sample .env files and
the portal .env. If every confirmed versioned identifier fails, use a
Project in an eligible region or ask the Voice Agent service owner to confirm
the subscription-region-model combination. Do not change the sample to
self-deployed mode to work around a managed-model failure.
Install:
- Azure CLI
- Git
- Python 3.10 or later
- Bash 4 or later
curlandjq
On Windows, use WSL for the Bash commands.
Sign in and select the customer subscription:
az login
SUBSCRIPTION="<subscription-name-or-id>"
az account set --subscription "${SUBSCRIPTION}"
az account show \
--query "{name:name,id:id,tenantId:tenantId,state:state}" \
--output tableStop if the displayed subscription is not the one the customer intends to use.
Subscription aliases used in conversation or internal documentation may not be accepted by Azure CLI. Resolve the exact name or ID with a structured query:
az account list --all \
--query "[?contains(name, 'Online') || contains(name, 'Meeting')].{name:name,id:id,state:state,isDefault:isDefault}" \
--output tableUse words relevant to the customer's subscription instead of Online and
Meeting. Then set SUBSCRIPTION to the returned exact name or ID.
Do this before creating resources when the customer already has a Foundry
Project. Project names are nested ARM resource names, so a plain
az resource list --name <project> can return no result even when the Project
exists.
When the Project name is unknown, enumerate all Projects in the active subscription first:
az resource list \
--resource-type Microsoft.CognitiveServices/accounts/projects \
--query "[].{name:name,resourceGroup:resourceGroup,location:location,id:id}" \
--output tableSelect only a Project whose account, resource group, workload owner, purpose, and confirmed Voice Agent region match the customer setup. Resource discovery does not prove that an unrelated Project is approved for this workload. Ask the customer for the intended Project ARM ID when those criteria do not select one unambiguously.
FOUNDRY_PROJECT="<existing-project-name>"
PROJECT_ID="$(
az resource list \
--resource-type Microsoft.CognitiveServices/accounts/projects \
--query "[?ends_with(name, '/${FOUNDRY_PROJECT}')].id | [0]" \
--output tsv
)"
: "${PROJECT_ID:?Project was not found in the active subscription}"
PROJECT_ENDPOINT="$(
az rest \
--method get \
--url "https://management.azure.com${PROJECT_ID}?api-version=2025-06-01" \
--query 'properties.endpoints."AI Foundry API"' \
--output tsv
)"
FOUNDRY_SCOPE="${PROJECT_ID%/projects/*}"
FOUNDRY_RESOURCE="${FOUNDRY_SCOPE##*/}"
RESOURCE_GROUP="${PROJECT_ID#*/resourceGroups/}"
RESOURCE_GROUP="${RESOURCE_GROUP%%/*}"
LOCATION="$(
az rest \
--method get \
--url "https://management.azure.com${PROJECT_ID}?api-version=2025-06-01" \
--query location \
--output tsv
)"
printf 'PROJECT_ENDPOINT=%s\nPROJECT_ID=%s\nFOUNDRY_SCOPE=%s\nRESOURCE_GROUP=%s\nFOUNDRY_RESOURCE=%s\nLOCATION=%s\n' \
"${PROJECT_ENDPOINT}" \
"${PROJECT_ID}" \
"${FOUNDRY_SCOPE}" \
"${RESOURCE_GROUP}" \
"${FOUNDRY_RESOURCE}" \
"${LOCATION}"If multiple Projects with the same name are visible, query all matches and
select by account, resource group, and subscription instead of taking [0]:
az resource list \
--resource-type Microsoft.CognitiveServices/accounts/projects \
--query "[?ends_with(name, '/${FOUNDRY_PROJECT}')].{name:name,id:id,resourceGroup:resourceGroup,location:location}" \
--output tableVerify the returned endpoint has this exact shape:
https://<account>.services.ai.azure.com/api/projects/<project>
Then continue with identity and role verification in sections 7 through 11. Do not run the resource creation sections for an existing Project.
The Foundry account name must be globally unique because it becomes part of the service endpoint.
LOCATION="<confirmed-supported-region>"
RESOURCE_GROUP="rg-voice-agent-samples"
FOUNDRY_RESOURCE="<globally-unique-foundry-resource-name>"
FOUNDRY_PROJECT="voice-agent-samples"Keep the variables in the same shell for the rest of this guide.
az provider register \
--namespace Microsoft.CognitiveServices \
--wait
az provider show \
--namespace Microsoft.CognitiveServices \
--query registrationState \
--output tsvThe final output must be:
Registered
az group create \
--name "${RESOURCE_GROUP}" \
--location "${LOCATION}"Create an AIServices account with Project management and a system-assigned identity:
az cognitiveservices account create \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--kind AIServices \
--sku S0 \
--location "${LOCATION}" \
--custom-domain "${FOUNDRY_RESOURCE}" \
--assign-identity \
--allow-project-management true \
--yesVerify:
az cognitiveservices account show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--query "{location:location,state:properties.provisioningState,projectManagement:properties.allowProjectManagement,id:id}" \
--output tableExpected values are the chosen location, Succeeded, and true.
Make those expectations hard gates:
ACCOUNT_STATE="$(
az cognitiveservices account show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--query properties.provisioningState \
--output tsv
)"
PROJECT_MANAGEMENT="$(
az cognitiveservices account show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--query properties.allowProjectManagement \
--output tsv
)"
test "${ACCOUNT_STATE}" = "Succeeded"
test "${PROJECT_MANAGEMENT,,}" = "true"az cognitiveservices account project create \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--location "${LOCATION}"Capture the identifiers used by SDK and management-plane commands:
PROJECT_ENDPOINT="$(
az cognitiveservices account project show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--query "properties.endpoints.\"AI Foundry API\"" \
--output tsv
)"
PROJECT_ID="$(
az cognitiveservices account project show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--query id \
--output tsv
)"
FOUNDRY_SCOPE="$(
az cognitiveservices account show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--query id \
--output tsv
)"
printf 'PROJECT_ENDPOINT=%s\nPROJECT_ID=%s\nFOUNDRY_SCOPE=%s\n' \
"${PROJECT_ENDPOINT}" \
"${PROJECT_ID}" \
"${FOUNDRY_SCOPE}"Expected formats:
PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
PROJECT_ID=/subscriptions/<id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>/projects/<project>
FOUNDRY_SCOPE=/subscriptions/<id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>
Require all three values:
: "${PROJECT_ENDPOINT:?Project endpoint was not returned}"
: "${PROJECT_ID:?Project ARM ID was not returned}"
: "${FOUNDRY_SCOPE:?Foundry account ARM ID was not returned}"
PROJECT_STATE="$(
az cognitiveservices account project show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--query properties.provisioningState \
--output tsv
)"
test "${PROJECT_STATE}" = "Succeeded"Account deployment lists describe customer-created deployments. They do not prove whether the service-managed Voice Agent model is enabled. Use the publication and session gates in 03: Start and run the samples after preview eligibility and region support are confirmed.
Decode the deployment identity's object ID from an ARM access token. This avoids requiring a separate Microsoft Graph sign-in:
ARM_ACCESS_TOKEN="$(
az account get-access-token \
--resource https://management.azure.com/ \
--query accessToken \
--output tsv
)"
USER_OBJECT_ID="$(
ARM_ACCESS_TOKEN="${ARM_ACCESS_TOKEN}" python3 - <<'PY'
import base64
import json
import os
payload = os.environ["ARM_ACCESS_TOKEN"].split(".")[1]
payload += "=" * (-len(payload) % 4)
print(json.loads(base64.urlsafe_b64decode(payload))["oid"])
PY
)"
unset ARM_ACCESS_TOKEN
PROJECT_PRINCIPAL_ID="$(
az cognitiveservices account project show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--query identity.principalId \
--output tsv
)"
: "${USER_OBJECT_ID:?Could not determine deployment user object ID}"
: "${PROJECT_PRINCIPAL_ID:?The Project has no managed identity principal ID}"The examples need two permission planes:
| Role | Why |
|---|---|
Cognitive Services Contributor |
Create and manage the Foundry account, Project, and Project connections |
Cognitive Services User |
Call the AIServices Agent data-plane APIs, including Agent create/read/invoke |
Assign both to the deployment user at the Foundry account scope:
az role assignment create \
--assignee-object-id "${USER_OBJECT_ID}" \
--assignee-principal-type User \
--role "Cognitive Services Contributor" \
--scope "${FOUNDRY_SCOPE}"
az role assignment create \
--assignee-object-id "${USER_OBJECT_ID}" \
--assignee-principal-type User \
--role "Cognitive Services User" \
--scope "${FOUNDRY_SCOPE}"Give the Project managed identity Agent data-plane access:
az role assignment create \
--assignee-object-id "${PROJECT_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Cognitive Services User" \
--scope "${FOUNDRY_SCOPE}"The person running these commands must have
Microsoft.Authorization/roleAssignments/write. If an organization uses ABAC
conditions or privileged identity management, an administrator may need to
perform or activate the assignment.
Organizations can replace these roles with custom least-privilege roles, but
the replacement must include the corresponding management actions and
Microsoft.CognitiveServices/accounts/AIServices/agents/* data actions.
Use ARM REST for role verification. Unlike az role assignment list, these
queries do not ask Microsoft Graph to resolve principal display information.
The principal filter includes assignments inherited from parent scopes.
AUTHORIZATION_API_VERSION="2022-04-01"
role_definition_id() {
local role_name="$1"
az rest \
--method get \
--url "https://management.azure.com${FOUNDRY_SCOPE}/providers/Microsoft.Authorization/roleDefinitions?api-version=${AUTHORIZATION_API_VERSION}" \
--url-parameters "\$filter=roleName eq '${role_name}'" \
--query "value[0].id" \
--output tsv
}
principal_role_definition_ids() {
local principal_id="$1"
az rest \
--method get \
--url "https://management.azure.com${FOUNDRY_SCOPE}/providers/Microsoft.Authorization/roleAssignments?api-version=${AUTHORIZATION_API_VERSION}" \
--url-parameters "\$filter=principalId eq '${principal_id}'" \
--query "value[].properties.roleDefinitionId" \
--output tsv
}
CONTRIBUTOR_ROLE_ID="$(role_definition_id "Cognitive Services Contributor")"
USER_ROLE_ID="$(role_definition_id "Cognitive Services User")"
USER_ROLE_IDS="$(principal_role_definition_ids "${USER_OBJECT_ID}")"
PROJECT_ROLE_IDS="$(principal_role_definition_ids "${PROJECT_PRINCIPAL_ID}")"
: "${CONTRIBUTOR_ROLE_ID:?Could not resolve Cognitive Services Contributor}"
: "${USER_ROLE_ID:?Could not resolve Cognitive Services User}"
grep -Fxiq "${CONTRIBUTOR_ROLE_ID}" <<<"${USER_ROLE_IDS}"
grep -Fxiq "${USER_ROLE_ID}" <<<"${USER_ROLE_IDS}"
grep -Fxiq "${USER_ROLE_ID}" <<<"${PROJECT_ROLE_IDS}"
az cognitiveservices account project show \
--resource-group "${RESOURCE_GROUP}" \
--name "${FOUNDRY_RESOURCE}" \
--project-name "${FOUNDRY_PROJECT}" \
--query "{state:properties.provisioningState,endpoint:properties.endpoints.\"AI Foundry API\",id:id,principalId:identity.principalId}" \
--output jsonRole assignments can take several minutes to propagate. Wait and retry the
data-plane operation before changing the role set. If the Azure CLI is signed
in as the wrong identity or tenant, run az login again and reselect
${SUBSCRIPTION}. Then restore the shell variables or rerun the capture
commands above.
Store these values in a password manager, deployment system, or protected customer setup record:
SUBSCRIPTION
LOCATION
RESOURCE_GROUP
FOUNDRY_RESOURCE
FOUNDRY_PROJECT
PROJECT_ENDPOINT
PROJECT_ID
FOUNDRY_SCOPE
They are identifiers, not bearer credentials, but they still disclose customer topology and should not be added to a reusable public sample.
Do not copy an endpoint from a demo, TIP, or another customer's subscription.
Use the ${PROJECT_ENDPOINT} produced above as
AZURE_AI_PROJECT_ENDPOINT in the selected example's ignored .env.
Continue in exactly one scenario README:
- 02: MCP settings, deployment, and development
- 03: Start and run the samples
- Example 1: Finance with Handoff - Realtime
- Example 1: Finance with Handoff - Cascade
- Example 2: Finance with OTP and Officer Search
- Voice Agent portal
- Finance examples architecture and documentation index
Those directories are self-contained after subscription setup. They own their
MCP endpoint and connection requirements, SDK installation, publication,
readback, runtime validation, and troubleshooting. Keeping those steps with the
scenario prevents a shared guide from drifting away from its agent.json and
sample.py.