The portal is just a client
For fifteen classes you have used the portal as though it were Azure itself. It is not. The portal is a website that, every time you click Create or Save, composes a request and sends it to one place: the Azure Resource Manager, a REST API living at management.azure.com. The CLI you have been running in the labs sends requests to the same API. So do the SDKs, so does Terraform, so does the mobile app. There is exactly one door into your Azure estate, and everything you have used is a client knocking on it.1
- ARM
- Azure Resource Manager — the single control-plane API through which every resource is created, read, updated and deleted. The portal, the CLI, the SDKs and infrastructure-as-code tools are all just clients of it.
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
Every resource in Azure has a single, canonical address — its resource ID — and it is not a random string. It is a path with a fixed grammar, and reading that grammar fluently is the most transferable skill in this class. Figure 9 takes one apart.
Say the segments aloud until they are reflex: /subscriptions/ names the billing-and-access boundary, /resourceGroups/ the lifecycle unit, /providers/Microsoft.Storage the resource provider — the service family that implements this kind of resource — then the type and the name. The resource ID is what --ids takes in the CLI, what a role assignment's scope actually is, and what an ARM request's URL path is built from. It is one string doing the work of an address, a scope, and an API route at once.
API versions, and why scripts rot
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.
This is the mechanism, and it is also the answer to a question that puzzles beginners: why does a script that worked last year suddenly behave oddly or fail? Because it pinned an api-version that has since been retired, or because a newer default exposes a field the script did not expect. API versions are a promise of stability — a request for 2021-04-01 keeps meaning what it meant in 2021 — but promises expire. The professional habit is to pin an explicit, recent api-version in anything you automate, and to treat "which version" as a real decision rather than copying whatever a blog post used. Scripts do not rot on their own; they rot because the API moved on and the version stamp did not.
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.
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.
The Activity Log is a transcript of the API
Because every change is an ARM request, Azure can — and does — keep a log of them. The Activity Log is that record: every control-plane operation on your resources, with who called it, when, against which resource ID, and whether it succeeded or failed. Read correctly, it is the single best debugging tool in the platform.
- 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.
The superpower is this: when the portal throws an error, or a resource ends up in a state nobody admits to causing, you open the Activity Log, find the failed or surprising operation, and read its details — often the raw JSON of the request and the error ARM returned. It tells you the exact call, the exact caller, and the exact reason, where the portal only showed a red banner. "Who deleted the storage account, and when?" is not a mystery; it is a filter on the Activity Log. Learning to reach for it first is what makes an incident a five-minute lookup instead of an afternoon of guessing.
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
You will talk to the same API the portal uses — read resources with az rest, address one by its resource ID, and prove for yourself that the api-version is not optional.
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.Create something, then read it back by its resource ID:
az group create --name rg-campux-lab --location westeurope ID=$(az group show --name rg-campux-lab --query id -o tsv) echo "$ID" az resource show --ids "$ID"
On screen: the printed ID has the exact five-segment shape of Figure 9. --ids addresses a resource by that single canonical string — the same string that is a role-assignment scope and an ARM URL path.Tear down:
az group delete --name rg-campux-lab --yes --no-wait
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, find and click JSON View (top-right).On screen: the resource's full ARM representation — its Resource ID in the five-segment shape, and an API version selector. This is the resource as the API sees it, not as a form renders it.
Begin creating a resource — say a storage account — and fill in the Basics, but stop at Review + create.
Instead of creating, click Download a template for automation (a link on the review step).On screen: the ARM template and parameters the portal was about to submit — the exact request behind the button. Every click really is an API call; here is the one you were about to send.
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 record of everything that has gone through ARM and read a few operations as the API calls they are.
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.
The portal is just a client
You need the same tag on three hundred resources, and clicking is not an option. You reach for the REST and ARM layer beneath the portal, script the change, and run it once. The realization sticks: the portal is just a client, and everything it does is an API call you can make yourself — which is the door to automating anything in Azure.
Examination
Four drills, 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. ARM requires an explicit api-version on every request and refuses those without one — there is no silent default. A and B are the comfortable assumptions that make people leave it off; the API does neither. This is also why automation should pin a specific, recent version: the parameter is mandatory, and which value you choose decides which fields and behaviours you get. Scripts rot when that pinned version is retired.
One API behind every client, the Activity Log as a control-plane transcript, and az rest handling the token. The two false statements mark the boundaries: reading a blob is a data-plane action that never touched ARM, so the Activity Log does not see it — it lives in the storage account's own logs; and a resource ID always begins with its subscription, because that is the top of the addressable hierarchy. Confusing control plane with data plane is the classic mistake when hunting for a log that isn't there.
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 segment keywords are fixed as ARM defines them (it is resourceGroups), and casing of the keywords is part of the 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.
Send them to the Activity Log. Open it, filter to the last few minutes and status Failed, find the operation, and open its JSON. That record has what the banner omitted: the operation, the target resource ID, the caller, and the exact error ARM returned — a policy denial, a quota limit, a name collision, a missing permission. The cause is almost always stated plainly there.
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.
- A resource ID has a fixed grammar: subscription, resource group, provider, type, name. It is an address, a scope, and an API path at once.
- Every ARM request needs an api-version. It is required, not defaulted — and pinning an old one is why automation rots.
- 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 is a transcript of control-plane API calls. When the portal says "failed," the log says why. Read it first.
- "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." ↩