Calling APIs: Invoke-RestMethod & Working with JSON

PowerShell Intermediate/Advanced

Chapter 7 · Calling APIs: Invoke-RestMethod & Working with JSON

Fundamentals 8 covered ConvertTo-Json/ConvertFrom-Json as a genuine structured interchange format — this chapter is where that format actually starts crossing a network, to and from a real REST API. It also surfaces a real, useful exception to one of Fundamentals 9's own central rules: unlike most cmdlets, a failed API call doesn't quietly print a red error and move on — it stops, immediately, whether you asked it to or not.

Invoke-RestMethod vs. Invoke-WebRequest

# Invoke-RestMethod — parses JSON automatically, hands back real objects directly $user = Invoke-RestMethod -Uri "https://api.github.com/users/octocat" $user.name # already a usable property — no ConvertFrom-Json step needed # Invoke-WebRequest — the raw HTTP response: status code, headers, AND the body as text $response = Invoke-WebRequest -Uri "https://api.github.com/users/octocat" $response.StatusCode # 200 $response.Headers # the full response header collection $response.Content | ConvertFrom-Json # the body is still just raw text — parse it yourself

Invoke-RestMethod is really Invoke-WebRequest plus an automatic ConvertFrom-Json already applied — the right default whenever you just want the data. Reach for Invoke-WebRequest instead the moment you actually need the status code, the response headers, or a non-JSON body (HTML, plain text, a file download).

Sending JSON: POST Requests

$body = @{ name = "New Item"; price = 19.99 } | ConvertTo-Json Invoke-RestMethod -Uri "https://api.example.com/items" ` -Method Post -Body $body -ContentType "application/json"

Building the request body is exactly Fundamentals 8's own ConvertTo-Json — including the same default -Depth 2 gotcha for any genuinely nested request body.

Authentication Headers

$headers = @{ Authorization = "Bearer $token" } Invoke-RestMethod -Uri "https://api.example.com/secure" -Headers $headers

HTTP Errors Are Terminating by Default

try { Invoke-RestMethod -Uri "https://api.example.com/does-not-exist" } catch { "Request failed: $($_.Exception.Message)" } # Caught with NO -ErrorAction Stop added anywhere — this just works
A real, useful exception to Fundamentals 9's own rule
Fundamentals 9 spent an entire chapter establishing that most cmdlet errors are non-terminating by default, and that -ErrorAction Stop is almost always required before try/catch actually does anything. Invoke-RestMethod and Invoke-WebRequest don't follow that pattern — any non-2xx HTTP status code (a 404, a 500, an authentication failure) is treated as a genuinely terminating error automatically, no -ErrorAction Stop required. It's worth remembering specifically because it's the exception rather than the norm — assuming every cmdlet needs -ErrorAction Stop to be caught, out of habit, isn't wrong here, but it isn't necessary either.

Reading the Real API Error Message

try { Invoke-RestMethod -Uri "https://api.example.com/items" -Method Post -Body $body -ContentType "application/json" } catch { "HTTP failure: $($_.Exception.Message)" # a generic .NET message — "The remote server returned an error: (400) Bad Request." "API said: $($_.ErrorDetails.Message)" # the ACTUAL response body — often the genuinely useful part }

$_.Exception.Message only ever describes the HTTP failure in generic terms. Most real APIs put the actually useful explanation — which field was invalid, why the request was rejected — in the response body itself, and $_.ErrorDetails.Message is where PowerShell already parses that body out for you, no manual stream-reading required.

Pagination: Looping Through Multiple Pages

$allResults = @() $uri = "https://api.example.com/items?page=1" do { $response = Invoke-RestMethod -Uri $uri $allResults += $response.items $uri = $response.nextPageUrl } while ($uri)

Fundamentals 6's own do/while is a natural fit here — the loop needs to run at least once regardless, then keep going only as long as the API keeps handing back another page to fetch.

A first practical habit
Watch for a 429 Too Many Requests response on any API called in a tight loop, and add a small Start-Sleep between calls proactively rather than reactively — most APIs document their own rate limits, and it's worth checking before writing the loop, not after it starts failing.

Hands-On Exercises

Exercise 1

Explain the difference between Invoke-RestMethod and Invoke-WebRequest. Describe a concrete situation where you'd need Invoke-WebRequest specifically, rather than the simpler Invoke-RestMethod.

📄 View solution
Exercise 2

Explain why a plain try/catch around Invoke-RestMethod can catch an HTTP 404 with no -ErrorAction Stop anywhere, contrasting this directly with Fundamentals 9's own general rule about non-terminating errors.

📄 View solution
Exercise 3

Write a POST request using Invoke-RestMethod that sends a JSON body built from a hashtable, includes a Bearer token in the Authorization header, and is wrapped in try/catch that reads the real API error message from the response body if the request fails.

📄 View solution

Chapter 7 Quick Reference

  • Invoke-RestMethod — parses JSON automatically, returns real objects; the right default for API data
  • Invoke-WebRequest — the raw response: status code, headers, and body as text; needed for anything beyond parsed JSON data
  • -Body / -ContentType "application/json" / -Method Post — sending a JSON request body, built with Fundamentals 8's own ConvertTo-Json
  • -Headers @{ Authorization = "Bearer $token" } — the common bearer-token auth pattern
  • HTTP errors are terminating by default — a real exception to Fundamentals 9's own non-terminating-by-default rule; no -ErrorAction Stop needed here
  • $_.ErrorDetails.Message — the actual API response body text, already parsed out inside catch
  • Pagination — a do/while loop (Fundamentals 6) following a "next page" link until none remains
  • Next chapter: Building & Publishing Your Own Modules