Use this to check a search service quickly. The commands run in Bash and use curl and jq.
Setup
RG=rg-ai
SVC=srch-prod
ENDPOINT="https://$SVC.search.windows.net"
API=2026-04-01
# Option 1: Entra ID token (needs role-based access enabled on the service)
TOKEN=$(az account get-access-token --scope https://search.azure.com/.default \
--query accessToken -o tsv)
AUTH="Authorization: Bearer $TOKEN"
# Option 2: admin key (full data plane access, so don't paste it into tickets)
ADMIN_KEY=$(az search admin-key show -g $RG --service-name $SVC --query primaryKey -o tsv)
AUTH="api-key: $ADMIN_KEY"
Pick one option. The API version shown was the latest stable release when this was written. Features such as resync and resetdocs need a preview version.
Service
# All services in a resource group (-g is required)
az search service list -g $RG -o table
# State, capacity and access settings; property names can vary by CLI version
az search service show -g $RG -n $SVC --query "{sku:sku.name,status:status,\
state:provisioningState,replicas:replicaCount,partitions:partitionCount,\
public:publicNetworkAccess,auth:authOptions}"
# Scale query capacity
az search service update -g $RG -n $SVC --replica-count 3
# Scale storage (valid values: 1, 2, 3, 4, 6, 12)
az search service update -g $RG -n $SVC --partition-count 2
# Private endpoint connections
az search private-endpoint-connection list -g $RG --service-name $SVC -o table
Warning
Changing
--public-access,--ip-rules,--bypassor the auth settings can cut off apps immediately. Check how clients connect first and keep the previous values ready to restore. Also check that tier limits allow the replica and partition count you want, and how the change affects billing.
Keys and access
# Admin keys
az search admin-key show -g $RG --service-name $SVC
# Rotate one admin key at a time. Move apps to the other key first
az search admin-key renew -g $RG --service-name $SVC --key-kind secondary
# Query keys: read-only access to documents
az search query-key list -g $RG --service-name $SVC
az search query-key create -g $RG --service-name $SVC -n chat-app
# Query keys can't be regenerated, so delete and recreate them
az search query-key delete -g $RG --service-name $SVC --key-value "<key value>"
# Allow both API keys and Entra ID
az search service update -g $RG -n $SVC --auth-options aadOrApiKey \
--aad-auth-failure-mode http401WithBearerChallenge
# Grant a role
SVC_ID=$(az search service show -g $RG -n $SVC --query id -o tsv)
az role assignment create --assignee "<principal id>" \
--role "Search Index Data Reader" --scope "$SVC_ID"
Caution
Regenerating an admin key breaks every client still using it. Check the indexers, ingestion jobs and apps that use the key before you renew it.
| Role | Allows |
|---|---|
| Search Index Data Reader | Query and retrieve documents |
| Search Index Data Contributor | Read and write documents |
| Search Service Contributor | Manage objects such as indexes and indexers, and run and reset indexers |
| Owner, Contributor | Control plane management and admin key retrieval. These roles don't include data plane run and reset through RBAC |
A role assignment can take time to apply, so wait and retest before you change anything else.
Capacity
curl -s "$ENDPOINT/servicestats?api-version=$API" -H "$AUTH" \
| jq '.counters | {storageSize, vectorIndexSize, documentCount, indexesCount, indexersCount}'
curl -s "$ENDPOINT/indexes/kb-v12/stats?api-version=$API" -H "$AUTH"
Each counter has a usage and may have a quota. Sizes are in bytes.
Indexes and documents
# List indexes and read a definition
curl -s "$ENDPOINT/indexes?api-version=$API&\$select=name" -H "$AUTH" | jq -r '.value[].name'
curl -s "$ENDPOINT/indexes/kb-v12?api-version=$API" -H "$AUTH" > kb-v12.json
# Count documents
curl -s "$ENDPOINT/indexes/kb-v12/docs/\$count?api-version=$API" -H "$AUTH"
# Look up one document by key
curl -s "$ENDPOINT/indexes/kb-v12/docs/doc-123?api-version=$API" -H "$AUTH"
# Keyword search
curl -s -X POST "$ENDPOINT/indexes/kb-v12/docs/search?api-version=$API" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"search":"reset vpn password","select":"id,title","top":5,"count":true}'
# Delete one document
curl -s -X POST "$ENDPOINT/indexes/kb-v12/docs/index?api-version=$API" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"value":[{"@search.action":"delete","id":"doc-123"}]}'
Replace id and title with your own field names. The key field in the delete body must match your index's key field.
Aliases
curl -s "$ENDPOINT/aliases?api-version=$API" -H "$AUTH" | jq
# Point the alias at a new index
curl -s -X PUT "$ENDPOINT/aliases/kb?api-version=$API" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"name":"kb","indexes":["kb-v13"]}'
Query the alias from apps so you can switch indexes without a deployment.
Indexers
IDX=kb-indexer
# List indexers
curl -s "$ENDPOINT/indexers?api-version=$API&\$select=name" -H "$AUTH" | jq -r '.value[].name'
# Last run
curl -s "$ENDPOINT/indexers/$IDX/status?api-version=$API" -H "$AUTH" \
| jq '{service: .status, last: (.lastResult | {status, startTime, endTime,
itemsProcessed, itemsFailed, errorMessage})}'
# Per-document errors and warnings
curl -s "$ENDPOINT/indexers/$IDX/status?api-version=$API" -H "$AUTH" \
| jq '.lastResult | {errors: [.errors[]? | {key, errorMessage, statusCode}],
warnings: [.warnings[]? | {key, message}]}'
# Run now (202 Accepted)
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
"$ENDPOINT/indexers/$IDX/run?api-version=$API" -H "$AUTH"
# Stop future scheduled runs
curl -s "$ENDPOINT/indexers/$IDX?api-version=$API" -H "$AUTH" \
| jq 'del(."@odata.context", ."@odata.etag") | .disabled = true' > indexer.json
curl -s -X PUT "$ENDPOINT/indexers/$IDX?api-version=$API" \
-H "$AUTH" -H "Content-Type: application/json" -d @indexer.json
lastResult.status | Meaning |
|---|---|
inProgress | Currently running |
success | Finished |
transientFailure | Failed. Read errorMessage and errors |
reset | Reset, waiting for a run |
Each indexer runs one instance at a time. A second run fails with "Another indexer invocation is currently in progress; concurrent invocations are not allowed." A started run can't be paused or stopped, so disabled affects future runs only.
Reset (full re-index)
# 204 No Content; nothing is re-indexed until you run the indexer
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
"$ENDPOINT/indexers/$IDX/reset?api-version=$API" -H "$AUTH"
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
"$ENDPOINT/indexers/$IDX/run?api-version=$API" -H "$AUTH"
Caution
A reset clears the high-water mark, can't be undone, and causes a full re-index of every document. With a skillset, that can mean repeating paid enrichment and embedding calls. It doesn't remove orphaned documents from the index. Estimate the time and cost first.
Metrics
az monitor metrics list --resource "$SVC_ID" \
--metric ThrottledSearchQueriesPercentage SearchLatency SearchQueriesPerSecond \
--interval PT5M --aggregation Average -o table
Other useful metrics are DocumentsProcessedCount, IndexStorageUsage, IndexVectorUsage and SkillExecutionCount.
Logs (KQL)
Send OperationLogs to a workspace first. Otherwise there's nothing to query.
az monitor diagnostic-settings create --name search-logs --resource "$SVC_ID" \
--workspace "<workspace resource id>" \
--logs '[{"category":"OperationLogs","enabled":true}]'
// Query volume and latency per index
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.SEARCH" and TimeGenerated > ago(1h)
| where OperationName == "Query.Search"
| summarize queries = count(), p95_ms = percentile(DurationMs, 95)
by IndexName_s, bin(TimeGenerated, 5m)
// HTTP status by operation
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.SEARCH" and TimeGenerated > ago(24h)
| where OperationName in ("Query.Search", "Indexing.Index")
| summarize count() by OperationName, resultSignature_d
If a column doesn't exist in your workspace, add | getschema to the first two lines to see the available names.
Quick error map
| Symptom | Likely cause | Check or fix |
|---|---|---|
| 401 or 403 with a token | Role-based access isn't enabled, or the role is missing | authOptions, role assignment on the service |
| 403 with an API key | Wrong key, or a query key used for an admin call | Use the correct key, or check --disable-local-auth |
| Timeout or no connection | Public access disabled, or IP rules or private DNS | publicNetworkAccess, ipRules, private endpoint connection |
| 404 on an index | Wrong index name, or an old alias target | GET /indexes, GET /aliases |
| 207 on upload | Some documents failed in the batch | Read each result's status and errorMessage |
| 429 or 503 on queries | Query load exceeds capacity | ThrottledSearchQueriesPercentage, add replicas |
| Documents are missing from results | Indexer failures or storage full | Indexer status, servicestats |
Prevent it next time
- Alert on
ThrottledSearchQueriesPercentage, on storage and vector usage, and on indexer runs with failures. - Prefer Entra ID and managed identities. Don't give admin keys to apps that only query.
- Save index, indexer, skillset and data source definitions in source control.