Azure (Microsoft Graph) connector – capabilities and configuration
The Azure connector in CRUD Service provides a production‑grade Microsoft Graph client with robust retries, pagination, observability, and flexible configuration. It’s implemented in src/connectors/azure_connector.py.
What it does
- Authenticates to Microsoft Graph using either a bearer token or client credentials (via a credential manager).
- Executes HTTP actions based on a command definition: method, endpoint, params, body, headers.
- Adds advanced query headers for Graph features like
$searchand$count. - Automatically follows
@odata.nextLink(auto‑pagination) with an opt‑out. - Supports batch requests (
$batch). - Provides OpenTelemetry spans, metrics, and structured error mapping.
- Rate‑limits client‑side to protect against throttling.
Configuration (system‑level)
Provide a dictionary to the connector when constructing, typically via YAML in config/systems/*.yaml or programmatically. All keys are optional unless noted.
base_url(string)- Default:
https://graph.microsoft.com/v1.0(use.../betaif needed)
- Default:
auth_type(string)- Supported:
azure(client credentials),bearer(static token) - Required for production flows:
azure
- Supported:
token(string)- Only when
auth_type == "bearer"
- Only when
credentials(object; forauth_type == "azure")client_id(required),client_secret(required),tenant_id(required),resource(optional)
default_content_type(string; defaultapplication/json)max_concurrent_requests(int; default 4) – global semaphore limitrate_limit_key(string; defaultbase_url) – scope for the global rate limitertimeouts(object) – maps to httpx.Timeout:connect/read/write/poolin secondsretry(object)max_attempts(default 4)backoff_base(s, default 0.5)backoff_factor(default 2.0)jitter(s, default 0.1)retry_on_status(default[408,429,500,502,503,504])retry_after_header_names(default["retry-after","x-ms-retry-after-ms","retry-after-ms"])- Honors
Retry-Afterheaders automatically
logging(object)enabled,request,response,auth,format("basic" | "detailed"). Sensitive headers are masked.
auto_paginate(bool; default true)
Command configuration (per‑call)
The command_config defines a call you pass to perform_action.
method(required):GET|POST|PATCH|PUT|DELETEendpoint(required): e.g.,/users,/groups/{id}params(object, optional): querystring map, e.g.{ "$top": 5, "$search": "\"displayName:Admin\"" }body(object, optional): JSON payload for POST/PATCH/PUTheaders(object, optional): merged with auth/default headers- Auto‑sets
client-request-idand defaultContent-Type
- Auto‑sets
auto_paginate(bool, optional): override connector default
Notes:
- For GET/DELETE,
paramsis sent, andbodyis not. - For POST/PATCH/PUT,
bodyis sent as JSON.
Advanced query auto‑detection
If the query uses advanced features, the connector sets:
- Header:
ConsistencyLevel: eventual - Param:
$count=true
Detected keywords: $search, $filter, $count, $orderby, ne, not, endsWith.
You can also force advanced mode via params/headers.
API surface
perform_action(object_type, action, params, command_config, transaction_id=None, expected_outcomes=None, correlation_id=None, timeout=60.0)→ standardized response- Returns dict where data is at
result["response"]["data"]
- Returns dict where data is at
iter_pages(url, headers, timeout, correlation_id)→ async iterator of page dictsperform_batch(requests, correlation_id=None, timeout=60.0, prefer=None, continue_on_error=False)→ standardized responseclose()– closes the shared httpx client
Observability
- OpenTelemetry
- Spans:
azure.perform_action(and internalazure.collect_pages)
- Spans:
- Metrics (custom)
azure.perform_action.duration: histogram per method/actionazure.semaphore.wait.duration: histogram for rate limiter waitsazure.request.retry.delay: histogram per statusazure.request.retry.attempts: histogramazure.http_429: counter
Error handling and resilience
- All
httpx.HTTPStatusErrorare mapped into FastAPIHTTPExceptionwith:status_code: HTTP statusdetail:{ "error": "HTTP Error <code>", "upstream": { "url": "...", "detail": <upstream JSON> } }
- Retries
- Enabled for statuses in
retry_on_status - Honors
Retry-Afterheaders (includingx-ms-retry-after-ms)
- Enabled for statuses in
- Token refresh
- Ensures auth headers are present and not expiring soon; refreshes within 5 minutes of expiry
Rate limiting
- Global
asyncio.Semaphorekeyed byrate_limit_keybounds concurrency. - Set
max_concurrent_requeststo adjust throughput.
Pagination behavior
- When
auto_paginateis enabled and@odata.nextLinkis present, subsequent pages are fetched and returned as a consolidated dict:
{ "value": [/* all items */], "@odata.deltaLink": "<last delta link if present>" }
- To fetch only a single page:
- Set
command_config.auto_paginate = false - Use
$topto reduce page size
- Set
Examples
- Minimal GET:
result = await connector.perform_action(
object_type="user",
action="get_one",
params={},
command_config={"method": "GET", "endpoint": "/users?$top=5"},
)
users = result["response"]["data"]
- Advanced search:
result = await connector.perform_action(
object_type="user",
action="search",
params={},
command_config={
"method": "GET",
"endpoint": "/users",
"params": {"$search": "\"displayName:Admin\"", "$count": "true"},
"headers": {"ConsistencyLevel": "eventual"},
},
)
- Create user:
create_cmd = {
"method": "POST",
"endpoint": "/users",
"body": {
"accountEnabled": True,
"displayName": "SDK Test",
"mailNickname": "sdktest",
"userPrincipalName": "sdktest@contoso.com",
"passwordProfile": {"forceChangePasswordNextSignIn": True, "password": "P@ssw0rd123!"},
},
}
result = await connector.perform_action("user", "create", {}, create_cmd)
user = result["response"]["data"]
- Single page only (no auto‑pagination):
single_page_cmd = {
"method": "GET",
"endpoint": "/users",
"params": {"$top": 5},
"auto_paginate": False,
}
result = await connector.perform_action("user", "list", {}, single_page_cmd)
- Batch:
out = await connector.perform_batch(
requests=[
{"id": "1", "method": "GET", "url": "/users?$top=1"},
{"id": "2", "method": "GET", "url": "/groups?$top=1"},
],
continue_on_error=False,
)
assert out["response"]["success"] is True
Authentication patterns
- Bearer token (testing)
auth_type: "bearer"token: "<access_token>"
- Client credentials (recommended)
auth_type: "azure"credentials: { client_id, client_secret, tenant_id, resource? }- Relies on credential manager and vault service to securely load and cache tokens
Request header defaults
client-request-id: correlation ID per callContent-Type:default_content_typewhen not providedConsistencyLevel: eventualauto‑added for advanced queries if not set
Standardized response shape
All methods return a standardized dict; the actual data is at result["response"]["data"]. For batch, result["response"]["success"] summarizes success.
Performance and tuning tips
- For smoke tests/dashboards, set
auto_paginate: falsewith small$topto avoid large scans. - Increase
max_concurrent_requestscautiously; Graph limits apply (~16 req/s per app). - Tune
timeoutsandretrybased on network latencies and Graph behavior.
Common operational gotchas
$searchrequiresConsistencyLevel: eventual. The connector often adds this automatically; you can also set it explicitly in headers.- Device owners: some tenants return 400 for
/devices/{id}/ownerson Device objects. Use/devices/{id}/registeredOwnersinstead. - License assignment requires a
usageLocation: set userPATCH { "usageLocation": "US" }beforeassignLicense. - Audit logs require
AuditLog.Read.All(app‑only). Without it, Graph returns 403. - Invitations require
User.Invite.All. Without it, Graph returns 401/403.
Security, secrets, and tokens
- Integrates with
CredentialManagerandVaultServiceto resolve credentials and cache tokens. - Tokens are refreshed when close to expiry.
- Sensitive headers are masked in logs.
Testing notes
- Unit tests: see
tests/connectors/test_azure_connector.py - Integration tests (live):
tests/integration/test_entraid_contractors_eid_live.pytests/integration/test_entraid_contractors_eid_extended.py
Changelog highlights
- Per‑command
auto_paginatewith connector‑level default - Improved advanced query detection and automatic header/param enrichment
- Enhanced retry with configurable status list and
Retry‑Afterhandling - More robust token refresh and default header merging
See also:
services/crud-service/reference/connectors/azure-connector-overviewservices/crud-service/reference/connectors/entra-admin-coverageservices/crud-service/reference/connectors/entra-graph-batch-examples