Fire an ARM/REST call from the CLI, read the JSON, and map it back to the portal action.
The portal is just a client
Three words carry this class. A request is a message your tool sends to a web address: a verb (GET to read, PUT to create or replace, DELETE to remove), the address, and sometimes a body. A response is what comes back: a status number (200 means it worked, 404 means not found, 403 means not allowed) and usually a body. That body is almost always JSON, text arranged as names and values inside braces, such as {"name": "rg-shop", "location": "westeurope"}. If you want a slower introduction with a story, Class 38 Part A stands on its own.
When you create or change an Azure resource in the portal, the screen is a client of Azure Resource Manager (ARM), the management API at management.azure.com. The Azure CLI, SDKs, and infrastructure tools also use ARM for management operations. Reading a blob or sending a message uses a service's data-plane endpoint instead. One screen can use both kinds of API; this class focuses on the management side.1
- ARM
- Azure Resource Manager — the management layer used to create, read, update, and delete Azure resources. Portal screens, CLI commands, SDKs, and infrastructure tools can call it. A service's data-plane API handles use of that resource, such as reading a blob.
This is the mental shift the whole class exists to cause, and it is the one that separates someone who operates Azure from someone who merely navigates it. Once you know that a click is an HTTP request, three things become possible that were not before: you can make the same change from a script that you made by hand; you can read exactly what the portal did when something goes wrong; and you can reason about permissions and policy as things that happen to requests, not to screens. The rest of this class is those three abilities, and each rests on being able to read one thing — the address of a resource.
The anatomy of a resource ID
Azure resources have resource IDs: paths that identify where a resource lives. Figure 2 shows a common resource-group-scoped storage account. Read this example first; subscription-level resources and nested child resources have different path shapes.
In this ID, /subscriptions/ identifies the subscription, /resourceGroups/ the group, and /providers/Microsoft.Storage the resource provider that implements the service. The type and name finish the address. You can pass the complete ID to commands accepting --ids, use it as a role-assignment scope, or place it in the route of a resource-specific ARM request. A resource group itself has a shorter ID; a child resource has a longer one.
Choose the API version the operation supports
Every request to ARM must carry one more thing besides the resource path: an API version. It is a required query parameter — ?api-version=2023-01-01 — and leaving it off is not a warning, it is an error; the request is refused.2
- api-version
- A dated version stamp, required on every ARM request, that pins which revision of the API — and therefore which fields and behaviours — you are asking for. It is how Azure evolves a service without breaking the callers written against last year's shape.
Each resource provider publishes supported API versions for its operations. A version used in one URL is not a universal version for every Azure service. Before automating an operation, check its current REST reference, choose a supported stable version where possible, and test the request. If a script later fails, check the response and the provider's version support; version retirement is one possible cause, alongside permissions, policy, and changed resource state.
az rest — the escape hatch
The CLI has a friendly command for most things — az storage account create and its kin. But sometimes a service is newer than the friendly command, or exposes something the command does not surface. For those moments there is an escape hatch that reaches ARM directly. Before you use it, it is worth seeing the whole request laid out, because a friendly command is only these five parts filled in for you.
The five parts of every ARM request
Strip away the tooling and every call to ARM is an ordinary HTTP request with the same five parts. Learn to name them and no CLI, SDK or template is a black box again — each is just assembling this and pressing send.
| Part | Example | What it carries |
|---|---|---|
| Method | PUT | The verb — what you want done to the resource. See below. |
| URL path | /subscriptions/…/providers/Microsoft.Storage/storageAccounts/campuxstore | The resource ID from §2, reused verbatim as the route. The address is the path. |
| api-version | ?api-version=2023-01-01 | The required version stamp from §3. No default; omit it and the request is refused. |
| Authorization header | Bearer eyJ0eXAi… | The Entra bearer token that proves who is calling. Everything below §4 is about this line. |
| Body (JSON) | { "location": "westeurope", "sku": … } | The desired state, on PUT/PATCH only. A GET or DELETE carries no body. |
The HTTP method says what kind of operation you request. GET reads; DELETE requests removal; a resource PUT commonly creates or updates the resource at a specified ID; and PATCH commonly updates selected fields. POST often invokes an action such as listKeys or restart. For the simple storage-account PUT in the game, sending the same valid request to the same ID does not create a second account. This is idempotency of that resource operation, not a guarantee that every deployment has no side effects or that a tool sends a PUT when nothing changed. Always read the particular operation's contract before retrying it.
Every click is an API call you can read.
az rest lets you send a raw request to any ARM endpoint while the CLI quietly handles the hard part — acquiring and attaching your access token, so you never touch a credential.3 You give it a method and a URL — the resource path plus an api-version — and it returns the JSON the API returns. It is how you call a preview feature the CLI has not caught up to, script an operation with no dedicated command, or simply see the exact shape of a resource as ARM sees it. Knowing az rest exists is what turns "the CLI can't do that" into "the CLI is just a client, and I can send the request myself." It is the difference between being limited by the tool and being limited only by the API.
Where does the Bearer header come from? After you sign in, az rest uses your CLI identity to obtain a token for the target service and attaches it to the request. For an ARM URL, the token is for the ARM audience. You do not need to print, copy, paste, or upload a live token to learn this. Treat a bearer token like a password: anyone holding it may be able to act as its bearer until it expires.
GET https://management.azure.com/subscriptions/{subscription-id}/resourcegroups?api-version=2021-04-01
Authorization: Bearer [redacted by the CLI]
Predict, run, verify: predict that an authenticated az rest call to that ARM URL can list resource groups within your permissions. Run the read-only Lab 1 command, then check that the JSON contains resource-group names you recognize. The CLI handles the credential while you inspect the request's method, path, version, and response.
Three boundaries matter. First, an ARM token is meant for the ARM audience; a storage data-plane operation requires the right audience and its own authorization. Second, Azure checks authorization for management requests at the target scope, but role-assignment changes can take time to propagate; do not promise immediate revocation based on a screen update. Third, the Activity Log records management events, usually writes and actions rather than reads. It does not show each blob read. Azure retains Activity Log events for 90 days by default; configure export in advance if you need them longer.
Play it through
Send a simulated storage-account PUT twice to the same resource ID and watch the account count stay at one. Then decide what that result proves, and where it stops applying.
The Activity Log is a transcript of the API
Azure's Activity Log records management events such as resource changes, deployments, and service-health events. Entries commonly include the caller, time, target, and status. It does not capture every API call or the contents of every request. Use it as a first stop when a management operation fails.
- What it captures
- Control-plane operations — creates, updates, deletes, role assignments, deployments — as API calls: the operation name, the caller, the timestamp, the target resource ID, and the status with any error.
- What it does not
- Data-plane actions inside a resource — the blobs read, the rows queried. Those live in the resource's own logs, not the Activity Log, because they never went through ARM.
When a portal create or update fails, filter the Activity Log by time, resource, and status. Inspect the event's JSON and error details; they can point to a policy denial, permission, quota, or invalid setting. If the event has a correlation ID, use it to connect related operations. Some failures require the deployment operation details or the service's own logs, so keep the original error too.
One field repays learning by name: the correlation ID. A single portal action — deploying a VM, say — fans out into many ARM operations: the network interface, the disk, the VM itself. Azure stamps them all with one shared correlation ID, so when a deployment half-succeeds and leaves a mess, you filter the Activity Log to that one ID and read the whole chain in order, seeing exactly which sub-operation failed and what the ones before it had already created. This is also why "it failed" is rarely the useful question; "which operation in the chain failed, and what did it leave behind?" is the one that ends the outage. The Activity Log answers the second, and it answers it in the API's own words rather than a screen's summary — which is the entire argument of this class, written down as a debugging habit you will use every week you are employed.
Reading the error the portal hid
A Campux teammate tries to create a resource in the portal and gets a vague red banner: "The deployment failed." No detail, no cause, just failure. Instead of retrying blindly, you open the Activity Log, filter to the last few minutes, and find the failed operation. Its JSON is unambiguous where the banner was mute: the request was refused by an Azure Policy — the allowed-regions deny from Class Eight — because the teammate picked a region outside the permitted set.
The banner said "it failed"; the Activity Log said why, in the API's own words, naming the policy and the disallowed value. Two minutes, not an afternoon. You show the teammate how to read it themselves, and the lesson of the whole class lands: the portal is a client, the API keeps the receipts, and an engineer who reads the receipts is never stuck at "it failed." That closes Phase Two — you now understand not just the services, but the one system underneath all of them.
The official reference, and a CAMPUX overview
What is Azure Resource Manager?
learn.microsoft.com/azure/azure-resource-manager/management/overview
Hands-on: Manage Azure resources by using the REST API
learn.microsoft.com/azure/azure-resource-manager/management/manage-resources-rest
A short walkthrough of the ARM API behind the portal, reading a resource ID, and the Activity Log will live here. Video to be added.
Call the ARM API directly
The game repeated a simulated PUT. Here you will make safe, read-only ARM requests, inspect a resource ID, and observe what happens when the required API version is missing. Creating a storage account is optional and costs money while it exists.
Open Cloud Shell — the >_ icon in the top bar — and choose Bash. Grab your subscription id:
SUB=$(az account show --query id -o tsv)
Call ARM directly to list your resource groups — this is exactly what the portal's list does:
az rest --method get \ --url "https://management.azure.com/subscriptions/$SUB/resourcegroups?api-version=2021-04-01"
What just happened: you sent a raw GET to management.azure.com and got JSON back. az rest attached your access token for you — no credential in sight — which is the whole point of the escape hatch.Now drop the api-version and watch it be refused:
az rest --method get \ --url "https://management.azure.com/subscriptions/$SUB/resourcegroups"
The lesson: the API returns an error demanding an api-version. It is a required query parameter, not a nicety — the fact from §3, proven at the command line.Read the ID of a resource group you already have access to. This ID is shorter than the storage account in Figure 2:
az group list --query "[].{name:name,resourceId:id}" --output tableOn screen: each ID ends after the resource-group name. If you have access to a storage account, open its JSON View in Lab 2 to see the longer resource-group-scoped example. No new Azure resources are required.
Catch the portal being a client
This lab does not create anything to keep — it makes the portal show you the API it is about to call and the JSON it stores. Once you have seen it, the screens never look quite the same.
Go to portal.azure.com and open any existing resource (a resource group is fine).
On its Overview, look for JSON View. If available, open it.On screen: the resource ID and JSON representation. A resource group has a short ID; a storage account has the longer shape in Figure 2. The exact fields depend on resource type and API version.
Begin creating a resource — say a storage account — and fill in the Basics, but stop at Review + create.
If Download a template for automation is available on the review step, inspect it without deploying.On screen: an automation template representing the planned resource configuration. It is not the exact HTTP request the portal would send. Compare its resource type, name, location, and properties with the form.
Discard the create (close it — nothing was deployed).The lesson: you just read the portal's homework. Anything the portal can do, it does by composing this request — which means you can compose it too, from a script or a template.
Portal wording drifts; if a label here does not match your screen, the anchors are the resource's JSON View and the "Download a template for automation" link on Review + create.
Read the transcript of the API
Open the management event history and read a few operations. Ordinary reads and service data-plane actions are generally absent.
In the top search bar, open Activity log (or open a resource group and select its Activity log blade).On screen: a list of operations — create, update, delete — each with the caller, a timestamp, a target resource, and a status. This is the control plane, written down.
Click any operation, then open its JSON.On screen: the operation name, the resource ID it targeted, the caller identity, and — on a failure — the exact error ARM returned. This is the detail the portal's red banners hide.
Use the filters (time range, resource group, status = Failed) to isolate a specific event.The point: "who did what, when, and why did it fail" is a filter here, not a mystery. Reaching for this first is the debugging superpower of the whole class.
Zoom out: one API behind everything is one place to reason about all of it
You can read a resource ID. Now reason about what "everything is ARM" does to the whole estate, because a click is a request, and a request is something you can review, permission, and replay.
Clicks in the portal feel free, so drift accumulates, so the running estate diverges from any record of why it looks the way it does. What turns clicks back into reviewable requests?
Every tool — portal, CLI, Terraform, the SDKs — depends on ARM; when it is slow or an api-version is retired, everything that speaks to it feels the same tremor.
The resource-ID grammar is the ceiling on how you address, permission, and reason about anything; fluency in it is the most transferable skill in the class.
Management changes leave event records you can inspect, but the Activity Log is not a complete replay of every request or data-plane action.
An operation's supported API versions and fields can change. Review the current provider reference and test automation before an upgrade.
The engineer who ships is asked "does the portal do it?" The engineer who gets promoted is asked "and what request did the portal actually send?" — and could read it back.
The portal is just a client
You need the same tag on three hundred resources, and clicking each one is impractical. A script or infrastructure tool can use ARM to update those management properties at scale. First check the right resource IDs, permissions, and API operation; then test on a small set before applying the change to all three hundred.
This is the whiteboard round of an interview. Rehearse it where it is cheap.
Treating the portal as Azure itself is the mistake this class exists to kill. The portal just composes requests to ARM — and so do the CLI, Terraform, and the SDKs. If your sketch makes the portal special, you cannot explain how a script makes the same change, or how the Activity Log records it as an API call. Draw the portal as one client among several, all knocking on the same door, and the whole class snaps into place.
Examination
The Whiteboard above, four drills here, then two situations. The situations have no marking scheme — write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored; this is between you and the page.
Microsoft.Storage. The provider is the service family, named after /providers/. C is the resource type within that provider, A is the resource group, and D is the resource's own name. Reading the segments in order — provider, then type, then name — is what lets you tell "what kind of thing" a resource is from its ID alone, without opening it.
C. This ARM REST request needs an api-version. The omitted-version call in Lab 1 shows the error. Choose a version supported by the particular operation; an older version is not automatically broken just because it is old.
Portal, CLI, and SDK management actions can use ARM; the Activity Log records management events; and az rest can attach a token for you. A blob read is a data-plane operation, so check the storage service's diagnostic logs if enabled. The other false statement fails because subscription-scoped resources need no resource-group segment, and higher-scope IDs have still different shapes.
RESOURCE ID under review
/subscription/8f2a-.../resourceGroups/rg-store/
providers/Microsoft.Storage/storageAccounts/campuxstore
A. The first segment is /subscriptions/, plural — a singular /subscription/ is not a valid ARM path and any call built from it will fail to resolve. B is wrong: the canonical spelling is resourceGroups, but ARM treats path segments as case-insensitive — Lab 1 called /resourcegroups in lowercase and succeeded, so casing is convention, not grammar; C is wrong because provider namespaces are dotted by design (Microsoft.Storage); D is nonsense.
Consider the consequence. A single mistyped segment means every command using that ID — a show, a role assignment scope, an az rest URL — targets a path that does not exist, and the error you get back ("resource not found") sends you hunting for a missing resource when the resource is fine and the address is wrong. Reading the ID's grammar segment by segment is how you catch this in a second.
Do not retry a failure you have not read. Retrying blind either reproduces the same error or, worse, half-succeeds and leaves a mess. The banner is the portal being a thin client — it showed you that a request failed but not the API's actual response. The detail exists; it is just not on screen.
Start with the Activity Log. Filter by time and Failed status, find the operation, and inspect its JSON and correlation ID. It may show a policy denial, quota problem, or permission failure. If the event only summarizes the failure, open the deployment operation details or the service's own logs before retrying.
Then fix the cause, not the symptom. If it was the allowed-regions policy, pick a permitted region; if a quota, request more or choose a smaller size; if permissions, get the right role at the right scope. The habit you are teaching is the whole class: when Azure says "failed," the API already said why — read the receipt before you touch anything.
They already use the API — they just cannot see it. Every click they make is an ARM request; the portal is only a form that composes one. So this is not a topic for "script people" — it is the substrate under the buttons they press all day. Understanding it does not add a tool; it explains the tool they already have.
Tie it to things they hit weekly. A permission error is a request ARM refused — knowing that sends them to check role and scope, not to refresh the page. A vague failure is a receipt in the Activity Log. A resource ID is what they paste when support asks "which resource," and reading its segments tells them what they are looking at. Every one of these is a portal experience made legible by the API model.
Frame the career point without preaching it. The engineer who knows the portal is a client can reproduce a fix, read a failure, and eventually automate the boring parts — which is the difference between operating Azure and clicking through it. They do not have to write scripts today; they do have to know that what they are doing is, underneath, exactly what a script would do.
Five things worth carrying out of this class
- The portal is one client of Azure Resource Manager. The CLI, SDKs and IaC tools are clients of the same single control-plane API.
- Read a resource ID by scope. A storage account includes a subscription and resource group; other resources can have shorter or longer paths.
- An ARM REST request needs an API version supported by that operation. Check the provider's current reference before automating.
- az rest sends a raw request to ARM and attaches your token — the escape hatch when the friendly command cannot do what you need.
- The Activity Log records management events, not every API request. For a failed change, inspect its event and deployment details.
- "Control plane" is the precise term for this API — the layer that creates and manages resources, reached at management.azure.com. It is distinct from the data plane, the per-service endpoints where you use a resource (blobs at *.blob.core.windows.net, and so on). This class is entirely about the control plane; the distinction returns whenever you go looking for a log or a permission and must first ask which plane it lives on. ↩
- Treat specific api-version dates as examples, not gospel — the current versions for any service are on its REST reference page and change over time. The durable facts are that the parameter is mandatory on every request and that pinning an explicit, recent value is the habit that keeps automation working; the exact date you pin is a decision to revisit, not a constant. ↩
- Under the hood, az rest obtains a token the same way az account get-access-token would and sends it as a bearer token — which is why you never paste a credential. This is also the cleanest way to see that authentication (proving who you are) and the request are separate steps: the token answers "who," the URL and body answer "what." ↩
Read an ARM request, say what a resource provider is, and see the portal as one client of the same API.
Play the lesson above to stamp this class.