When authoring templates or clients that call/describe APIs (Graph, resource providers, etc.), treat the “API contract” as a first-class design artifact:
apiVersion in the consumer-facing surface unless you have a planned compatibility/back-compat story; prefer hardcoding and only change versions intentionally.properties body as a single parameter value. Instead, parameterize the individual fields (or use a single object parameter type only when you truly need to accept a structured payload).resourceId(...) (deterministic, correct segments) instead of brittle string concatenation.Example patterns:
"triggerType": {
"dataOdataTypeExpression": "@{triggerBody()?['data']?['@odata.type']}",
"uploadDataType": "#microsoft.graph.customDataProvidedResourceRequestAccessReviewDataCalloutData",
"applyDecisionType": "#microsoft.graph.customDataProvidedResourceApplyDecisionContextData",
"updateAccessAssignmentType": "#microsoft.graph.customDataProvidedResourceAccessAssignmentCalloutData"
}
"parameters": {
"targetProperties": { "type": "array" }
}
// and then use json()/field-wise mapping in the resource, rather than passing
// a single opaque `properties` object string/blob as the parameter value.
apiVersion (or only expose it with a deliberate compatibility plan):
"apiVersion": "2023-05-02"
resourceId(...) for API-facing resource identifiers:
"proximityPlacementGroup": "[resourceId(parameters('ProximityPlacementGroupResourceGroup'),'Microsoft.Compute/proximityPlacementGroups', parameters('ProximityPlacementGroupName'))]"