๐ŸŽ New User? Get 20% off your first purchase with code NEWUSER20 ยท โšก Instant download ยท ๐Ÿ”’ Secure checkout Register Now โ†’
Menu

Categories

PowerShell REST API Calls: Invoke-RestMethod Complete Guide

PowerShell REST API Calls: Invoke-RestMethod Complete Guide

You need to pull user data from Microsoft Graph, create GitHub issues from a CSV, or query an internal monitoring API. PowerShell REST API calls handle all of these. PowerShell provides Invoke-RestMethod and Invoke-WebRequest for HTTP operations; this guide shows when to use each, how to authenticate with bearer tokens and API keys, parse JSON responses, handle errors, and deal with pagination and rate limits. You'll see real examples for common patterns that work in production scripts.

Most API automation starts with a GET request to read data, followed by authentication challenges, then POST or PUT to write changes. This part covers the fundamentals: choosing the right cmdlet, building requests with query parameters and headers, and the three most common authentication methods. Later parts add error handling, pagination, and advanced patterns for Microsoft 365, Azure, and other enterprise APIs.

Invoke-RestMethod vs Invoke-WebRequest: Which to Use

Invoke-RestMethod automatically parses JSON and XML responses into PowerShell objects. You call an API, get back a hashtable or custom object, and access properties directly. Invoke-WebRequest returns the raw HTTP response: status code, headers, and content as a string. You parse the content yourself. For most API work, use Invoke-RestMethod. Switch to Invoke-WebRequest when you need to inspect response headers or handle status codes that Invoke-RestMethod would throw as errors.

Here's the same call to a JSON endpoint with both cmdlets:

# Invoke-RestMethod: automatic parsing
$data = Invoke-RestMethod -Uri 'https://httpbin.org/get?user=alice'
$data.args.user  # alice

# Invoke-WebRequest: manual parsing
$response = Invoke-WebRequest -Uri 'https://httpbin.org/get?user=alice'
$response.StatusCode  # 200
$data = $response.Content | ConvertFrom-Json
$data.args.user  # alice

The first example gives you $data.args.user immediately. The second requires ConvertFrom-Json but exposes StatusCode and Headers. PowerShell 7 added -StatusCodeVariable and -ResponseHeadersVariable to Invoke-RestMethod, closing the gap:

$data = Invoke-RestMethod -Uri 'https://httpbin.org/get' `
    -StatusCodeVariable statusCode `
    -ResponseHeadersVariable headers
$statusCode  # 200
$headers['Content-Type']  # application/json

Use Invoke-RestMethod for API calls where you care about the data. Use Invoke-WebRequest when you're testing an endpoint, scraping HTML, or need full control over the HTTP layer. The Invoke-RestMethod documentation covers all parameters and return types.

GET Requests: Retrieving Data with Parameters

A GET request retrieves data from an API endpoint. You specify the URL, add query parameters for filtering or pagination, and set headers for content negotiation. The response is a PowerShell object you can filter, sort, or export.

Query String Parameters

You can embed query parameters directly in the URI or pass them as a hashtable with -Body. For GET requests, PowerShell converts the hashtable to a query string:

# Embedded in URI
$users = Invoke-RestMethod -Uri 'https://api.example.com/users?role=admin&active=true'

# Using -Body hashtable (cleaner for multiple parameters)
$params = @{
    role = 'admin'
    active = 'true'
    limit = 50
}
$users = Invoke-RestMethod -Uri 'https://api.example.com/users' -Body $params

The hashtable approach is cleaner when you build parameters conditionally. You can add or remove keys before the call. Accessing nested properties in the response works like any PowerShell object:

$response = Invoke-RestMethod -Uri 'https://httpbin.org/get?user=alice&dept=IT'
$response.args.user   # alice
$response.headers.Host  # httpbin.org
$response.url         # https://httpbin.org/get?user=alice&dept=IT

The httpbin.org/get endpoint echoes your request details as JSON, useful for testing query string construction.

Custom Headers and User-Agent

APIs often require specific headers: Accept for content type negotiation, User-Agent to identify your script, or custom headers for API versioning. Pass them with -Headers as a hashtable:

$headers = @{
    'Accept' = 'application/json'
    'User-Agent' = 'MyAutomation/2.1'
    'X-API-Version' = '2023-01-15'
}
$data = Invoke-RestMethod -Uri 'https://api.example.com/resources' -Headers $headers

Some APIs return different responses based on Accept. If you request application/xml, Invoke-RestMethod parses it into [xml] objects. The User-Agent header helps API providers track client versions and contact you if your script misbehaves. Always set it to something identifiable.

Bearer Token and API Key Authentication

Most modern APIs use bearer tokens (OAuth 2.0, personal access tokens) or API keys. You include them in headers or query strings. Getting authentication wrong returns 401 Unauthorized; getting it right returns your data.

Authorization Header for Bearer Tokens

Bearer tokens go in the Authorization header with the format Bearer <token>. Microsoft Graph, GitHub, and many SaaS APIs use this pattern:

$token = $env:GRAPH_TOKEN
$headers = @{
    'Authorization' = "Bearer $token"
}
$users = Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/users' -Headers $headers
$users.value | Select-Object displayName, userPrincipalName

Without the header, you get a 401 error with {"error":{"code":"InvalidAuthenticationToken"}}. With a valid token, you get JSON with a value array of user objects. For detailed examples of obtaining and using Graph tokens, see Microsoft Graph API Authentication.

API Keys in Headers or Query Strings

API keys can appear in a custom header or in the query string, depending on the service. Header-based keys are more secure because they don't appear in logs or browser history:

# Header-based API key
$headers = @{
    'X-API-Key' = $env:SERVICE_API_KEY
}
$data = Invoke-RestMethod -Uri 'https://api.example.com/data' -Headers $headers

# Query string API key (less common, less secure)
$uri = "https://api.example.com/data?api_key=$env:SERVICE_API_KEY"
$data = Invoke-RestMethod -Uri $uri

Check your API's documentation for the exact header name. Common variations include X-API-Key, X-Auth-Token, ApiKey, and Authorization: ApiKey <key>. Query string keys are easier to leak in logs; use headers when you have a choice.

Storing Secrets Securely

Never hardcode tokens or keys in scripts. Use environment variables for local testing, Azure Key Vault or AWS Secrets Manager for production, or PowerShell's SecretManagement module for cross-platform secret storage:

# Environment variable (set outside the script)
$token = $env:API_TOKEN

# SecretManagement module (install once: Install-Module Microsoft.PowerShell.SecretManagement)
$token = Get-Secret -Name 'MyAPIToken' -AsPlainText

# Azure Key Vault (requires Az.KeyVault module)
$secret = Get-AzKeyVaultSecret -VaultName 'myVault' -Name 'APIToken' -AsPlainText

For scripts checked into version control, read secrets at runtime from a secure store. If you must store tokens locally during development, use .env files that are excluded from Git via .gitignore. For more on working with JSON responses and complex data structures, see PowerShell and JSON: From Beginner to Advanced.

POST, PUT and DELETE: Sending Data

Creating, updating, and deleting resources requires sending data in the request body. You build a PowerShell hashtable or custom object, convert it to JSON, and pass it with -Body and -ContentType. The -Method parameter specifies the HTTP verb.

JSON Body with ConvertTo-Json

A POST request typically creates a new resource. Build your data as a hashtable, convert it to JSON with ConvertTo-Json, and set -ContentType 'application/json':

$newUser = @{
    username = 'jdoe'
    email = 'jdoe@example.com'
    role = 'editor'
}
$json = $newUser | ConvertTo-Json
$headers = @{ 'Authorization' = "Bearer $token" }

$result = Invoke-RestMethod -Uri 'https://api.example.com/users' `
    -Method Post `
    -Body $json `
    -ContentType 'application/json' `
    -Headers $headers

$result.id  # 12345

The API returns the created resource, often with an id or created_at timestamp. PUT requests update existing resources with similar syntax, but you include the resource ID in the URI:

$update = @{
    email = 'john.doe@example.com'
    role = 'admin'
}
$json = $update | ConvertTo-Json

$result = Invoke-RestMethod -Uri 'https://api.example.com/users/12345' `
    -Method Put `
    -Body $json `
    -ContentType 'application/json' `
    -Headers $headers

DELETE requests remove resources. They usually take no body, just the resource URI:

Invoke-RestMethod -Uri 'https://api.example.com/users/12345' `
    -Method Delete `
    -Headers $headers

If your data contains nested hashtables or arrays, ConvertTo-Json defaults to a depth of 2, which truncates deeper structures. Use -Depth to serialize complex objects fully:

$complex = @{
    user = @{
        name = 'Alice'
        address = @{
            city = 'Seattle'
            zip = '98101'
        }
    }
}
$json = $complex | ConvertTo-Json -Depth 5

Without -Depth 5, the address hashtable appears as a string like System.Collections.Hashtable in the JSON. The ConvertTo-Json documentation explains depth limits and compression options.

Form Data and Multipart Uploads

Some APIs expect form-encoded data instead of JSON. Pass a hashtable to -Body with -ContentType 'application/x-www-form-urlencoded':

$formData = @{
    username = 'jdoe'
    password = 'secret123'
}

$response = Invoke-RestMethod -Uri 'https://api.example.com/login' `
    -Method Post `
    -Body $formData `
    -ContentType 'application/x-www-form-urlencoded'

$response.access_token

PowerShell automatically URL-encodes the key-value pairs. For file uploads with multipart/form-data, PowerShell 7 introduced -Form, which accepts a hashtable containing strings or file paths:

$formData = @{
    file = Get-Item 'C:\reports\monthly.pdf'
    description = 'Monthly report for Q4'
}

$result = Invoke-RestMethod -Uri 'https://api.example.com/upload' `
    -Method Post `
    -Form $formData `
    -Headers $headers

PowerShell 5.1 requires -InFile for single-file uploads or manual construction of multipart bodies with System.Net.Http.MultipartFormDataContent. For production file upload scripts, PowerShell 7 is the simpler path.

Error Handling and HTTP Status Codes

Invoke-RestMethod throws a terminating error when the API returns a 4xx or 5xx status code. Without a try-catch block, your script stops. Wrap API calls in try-catch to log errors, retry, or fail gracefully.

Try-Catch for Non-Success Responses

PowerShell 7 throws [Microsoft.PowerShell.Commands.HttpResponseException] for HTTP errors. PowerShell 5.1 throws [System.Net.WebException]. Catch both for cross-version scripts:

try {
    $users = Invoke-RestMethod -Uri 'https://api.example.com/users' -Headers $headers
} catch [Microsoft.PowerShell.Commands.HttpResponseException] {
    $statusCode = $_.Exception.Response.StatusCode.value__
    Write-Error "API returned $statusCode: $($_.Exception.Message)"
} catch [System.Net.WebException] {
    $statusCode = [int]$_.Exception.Response.StatusCode
    Write-Error "API returned $statusCode: $($_.Exception.Message)"
} catch {
    Write-Error "Unexpected error: $_"
}

The $_ automatic variable inside the catch block is the ErrorRecord. $_.Exception.Response.StatusCode gives you the HTTP status code. In PowerShell 7, use .value__ to get the integer; in 5.1, cast to [int]. A 401 means your token expired or is invalid; a 404 means the resource doesn't exist; a 500 is a server error you can't fix from the client side.

Inspecting ErrorRecord and Response Body

The API often returns a JSON error body with details. Extract it from the exception's response stream:

try {
    $result = Invoke-RestMethod -Uri 'https://api.example.com/users/99999' -Headers $headers
} catch {
    $statusCode = $_.Exception.Response.StatusCode.value__
    $reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
    $errorBody = $reader.ReadToEnd() | ConvertFrom-Json
    Write-Error "HTTP $statusCode - $($errorBody.error.message)"
}

This reads the response body as a string, parses it as JSON, and logs the specific error message. For a 404, you might see {"error":{"code":"ResourceNotFound","message":"User 99999 does not exist"}}. Logging the full error helps when troubleshooting production failures.

PowerShell 7.0+ added -SkipHttpErrorCheck, which prevents throwing on 4xx/5xx. You check the status code manually:

$response = Invoke-RestMethod -Uri 'https://api.example.com/users/99999' `
    -Headers $headers `
    -StatusCodeVariable statusCode `
    -SkipHttpErrorCheck

if ($statusCode -ge 400) {
    Write-Warning "Request failed with status $statusCode"
} else {
    $response | Format-Table
}

This pattern is useful for conditional logic where a 404 is expected and not an error condition. For most automation, try-catch with specific exception types is cleaner and handles network failures as well as HTTP errors.

Pagination and Rate Limiting

APIs that return large datasets split results across multiple pages. You follow a next link or increment an offset parameter until no more pages remain. Rate limits prevent abuse; you check response headers and sleep or abort when approaching the limit.

Many APIs include a next or next_page field in the JSON response. Loop until it's null:

$allUsers = @()
$url = 'https://api.example.com/users?limit=100'

do {
    $response = Invoke-RestMethod -Uri $url -Headers $headers
    $allUsers += $response.data
    $url = $response.next_page
} while ($url)

$allUsers.Count  # 1247

This pattern works for APIs like Slack, Stripe, and many internal services. Each iteration appends $response.data to your collection and updates $url. When next_page is null, the loop exits.

GitHub and some RFC-compliant APIs use the Link HTTP header with rel="next". You need Invoke-WebRequest to access headers, then extract the next URL with a regex:

$allIssues = @()
$url = 'https://api.github.com/repos/owner/repo/issues?per_page=100'

do {
    $response = Invoke-WebRequest -Uri $url -Headers $headers
    $data = $response.Content | ConvertFrom-Json
    $allIssues += $data

    $linkHeader = $response.Headers['Link']
    if ($linkHeader -match '<([^>]+)>; rel="next"') {
        $url = $matches[1]
    } else {
        $url = $null
    }
} while ($url)

The regex captures the URL between angle brackets before ; rel="next". When the last page is reached, the Link header contains only rel="prev" and rel="last", so $url becomes $null and the loop stops.

Respecting Rate Limit Headers

Most APIs return rate limit information in response headers: X-RateLimit-Remaining for requests left in the current window, X-RateLimit-Reset for the Unix timestamp when the limit resets. Check these before making the next request:

$url = 'https://api.github.com/users/octocat/repos'
$headers = @{ 'Authorization' = "token $env:GITHUB_TOKEN" }

$response = Invoke-WebRequest -Uri $url -Headers $headers
$remaining = [int]$response.Headers['X-RateLimit-Remaining']
$resetTime = [int]$response.Headers['X-RateLimit-Reset']

if ($remaining -lt 10) {
    $waitSeconds = $resetTime - (Get-Date -UFormat %s)
    Write-Warning "Only $remaining requests left. Sleeping for $waitSeconds seconds."
    Start-Sleep -Seconds $waitSeconds
}

$repos = $response.Content | ConvertFrom-Json
$repos | Select-Object name, stargazers_count

When X-RateLimit-Remaining drops below a threshold, calculate how many seconds until X-RateLimit-Reset and sleep. GitHub's limit for authenticated requests is 5,000 per hour; unauthenticated is 60 per hour. If you hit zero remaining, the API returns 403 with {"message":"API rate limit exceeded"}. Checking headers proactively avoids this. For pagination loops, check rate limits inside the loop and sleep between pages if necessary.

Advanced Authentication Patterns

Beyond bearer tokens and API keys, you'll encounter basic authentication with username and password credentials, client certificate authentication for mutual TLS, and OAuth 2.0 flows that require interactive login or device code flows. PowerShell provides built-in support for the first two; OAuth tokens require a separate acquisition step.

Basic Auth and Credential Objects

Basic authentication encodes username and password as Base64(username:password) in the Authorization header. You can build this manually or use the -Credential parameter, which handles encoding for you:

# Interactive credential prompt
$credential = Get-Credential
$response = Invoke-RestMethod -Uri 'https://api.example.com/data' -Credential $credential

# Programmatic credential (from environment variables or secret store)
$username = $env:API_USER
$password = $env:API_PASS | ConvertTo-SecureString -AsPlainText -Force
$credential = [PSCredential]::new($username, $password)
$response = Invoke-RestMethod -Uri 'https://api.example.com/data' -Credential $credential

The Get-Credential cmdlet prompts for credentials interactively, useful for development but not for automated scripts. For automation, construct a PSCredential object from stored secrets. ConvertTo-SecureString -AsPlainText -Force converts a plain string to the SecureString required by PSCredential. Never store passwords as plain text in scripts; read them from environment variables, Key Vault, or SecretManagement at runtime.

If the API doesn't support the Authorization header format PowerShell generates, build the header manually:

$user = 'apiuser'
$pass = $env:API_PASS
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${user}:${pass}"))
$headers = @{ 'Authorization' = "Basic $base64" }
$response = Invoke-RestMethod -Uri 'https://api.example.com/data' -Headers $headers

This gives you full control over the header value. Some APIs use Basic with a token instead of username:password; check the documentation.

Client Certificates

Mutual TLS requires the client to present a certificate during the SSL handshake. The certificate is typically stored in the Windows certificate store or as a PFX file. Use -Certificate to pass it to Invoke-RestMethod:

# From Windows certificate store (by thumbprint)
$cert = Get-ChildItem Cert:\CurrentUser\My\A1B2C3D4E5F6...
$response = Invoke-RestMethod -Uri 'https://api.example.com/secure' -Certificate $cert

# From PFX file
$cert = Get-PfxCertificate -FilePath 'C:\certs\client.pfx'
$response = Invoke-RestMethod -Uri 'https://api.example.com/secure' -Certificate $cert

The certificate must include the private key. If you get The request was aborted: Could not create SSL/TLS secure channel, the server rejected your certificate or you didn't provide one. For Azure services with managed identity, you register the certificate thumbprint in the app registration and retrieve the certificate from Key Vault at runtime. For on-premises APIs, export the certificate with private key as a PFX and protect it with a password or ACLs.

OAuth 2.0 token acquisition happens outside Invoke-RestMethod. For interactive flows or device code flows with Microsoft identity, use the MSAL.PS module or Microsoft.Identity.Client library to obtain an access token, then pass it as a bearer token:

# Install once: Install-Module MSAL.PS
$token = Get-MsalToken -ClientId 'your-client-id' -TenantId 'your-tenant-id' -Interactive
$headers = @{ 'Authorization' = "Bearer $($token.AccessToken)" }
$users = Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/users' -Headers $headers

For detailed examples of app registration, certificate-based authentication, and managed identity with Microsoft Graph, see Microsoft Graph API Authentication.

Production Script Patterns

Production scripts need retry logic for transient failures, logging for troubleshooting, timeouts to avoid hanging indefinitely, and proxy support for corporate networks. Wrapping Invoke-RestMethod in a helper function centralizes these concerns and makes your automation more reliable.

Retry Logic for Transient Errors

Network hiccups and 5xx server errors are often transient. Retry the request a few times with exponential backoff before giving up:

function Invoke-ApiWithRetry {
    param(
        [string]$Uri,
        [hashtable]$Headers,
        [int]$MaxRetries = 3
    )
    
    $attempt = 0
    do {
        try {
            $response = Invoke-RestMethod -Uri $Uri -Headers $Headers -TimeoutSec 30
            return $response
        } catch {
            $attempt++
            $statusCode = $_.Exception.Response.StatusCode.value__
            
            if ($attempt -ge $MaxRetries) {
                Write-Error "Failed after $MaxRetries attempts: $_"
                throw
            }
            
            if ($statusCode -ge 500 -or $_.Exception.Message -match 'timeout|network') {
                $waitSeconds = [math]::Pow(2, $attempt)
                Write-Warning "Attempt $attempt failed with $statusCode. Retrying in $waitSeconds seconds..."
                Start-Sleep -Seconds $waitSeconds
            } else {
                throw  # Don't retry 4xx client errors
            }
        }
    } while ($attempt -lt $MaxRetries)
}

This function retries on 5xx errors or network exceptions, waiting 2, 4, then 8 seconds between attempts. Client errors like 401 or 404 throw immediately because retrying won't help. The -TimeoutSec 30 parameter prevents the request from hanging indefinitely if the server stops responding.

Logging and Timeout Configuration

Use Write-Verbose for request details and Write-Error for failures. Verbose output is hidden by default but visible with -Verbose, useful for debugging:

function Invoke-ApiRequest {
    [CmdletBinding()]
    param(
        [string]$Uri,
        [hashtable]$Headers,
        [string]$Method = 'Get',
        [object]$Body,
        [int]$TimeoutSec = 30
    )
    
    Write-Verbose "API Request: $Method $Uri"
    if ($Body) {
        Write-Verbose "Body: $($Body | ConvertTo-Json -Compress)"
    }
    
    try {
        $params = @{
            Uri = $Uri
            Headers = $Headers
            Method = $Method
            TimeoutSec = $TimeoutSec
        }
        if ($Body) {
            $params['Body'] = ($Body | ConvertTo-Json)
            $params['ContentType'] = 'application/json'
        }
        
        $response = Invoke-RestMethod @params
        Write-Verbose "Success: $($response | ConvertTo-Json -Compress -Depth 2)"
        return $response
    } catch {
        Write-Error "API call failed: $_"
        throw
    }
}

# Usage
$headers = @{ 'Authorization' = "Bearer $token" }
$result = Invoke-ApiRequest -Uri 'https://api.example.com/users' -Headers $headers -Verbose

The [CmdletBinding()] attribute enables -Verbose support. For structured logging in production, consider writing JSON log entries to a file or sending them to a centralized logging service. The -TimeoutSec parameter defaults to 30 seconds; increase it for slow APIs or large file uploads.

Proxy Support and Testing

Corporate environments often require an HTTP proxy. PowerShell respects system proxy settings by default, but you can specify a proxy explicitly with -Proxy and -ProxyCredential:

$proxy = 'http://proxy.corp.example.com:8080'
$proxyCredential = Get-Credential -Message 'Proxy credentials'

$response = Invoke-RestMethod -Uri 'https://api.example.com/data' `
    -Headers $headers `
    -Proxy $proxy `
    -ProxyCredential $proxyCredential

If you get 407 Proxy Authentication Required, the proxy needs credentials. For automated scripts, retrieve proxy credentials from a secret store rather than prompting interactively.

Test your API wrapper functions with Pester and mock responses. Pester 5 introduced Mock with support for Invoke-RestMethod:

Describe 'Invoke-ApiRequest' {
    It 'returns data on success' {
        Mock Invoke-RestMethod { return @{ id = 1; name = 'Test' } }
        
        $result = Invoke-ApiRequest -Uri 'https://api.example.com/test' -Headers @{}
        $result.name | Should -Be 'Test'
    }
    
    It 'throws on 500 error' {
        Mock Invoke-RestMethod { throw [System.Net.WebException]::new('500 error') }
        
        { Invoke-ApiRequest -Uri 'https://api.example.com/test' -Headers @{} } | Should -Throw
    }
}

Mocking lets you test error handling, retry logic, and logging without hitting real APIs. For production scripts that automate Microsoft 365, see Automate Microsoft 365 with PowerShell and the Microsoft Graph API for complete examples of production-ready functions with retry, logging, and error handling.

Quick Reference: Common Invoke-RestMethod Patterns

Start every API integration by confirming authentication works with a simple GET request. Once you get a 200 response, add error handling with try-catch and retry logic for 5xx errors. For pagination, check the API documentation for next links or offset parameters, then loop until you've collected all pages. Always set -TimeoutSec to prevent hanging; 30 seconds is reasonable for most APIs. Use -Verbose during development to see request details, then remove it for production or pipe output to a log file.

When sending JSON with POST or PUT, remember | ConvertTo-Json and -ContentType 'application/json'. For nested objects, add -Depth to avoid truncation. Check rate limit headers (X-RateLimit-Remaining) in pagination loops and sleep if you're close to the limit. For bearer tokens, store them in environment variables or a secret manager, never in the script itself. If you're working with Microsoft Graph, certificate-based authentication and managed identity are more secure than client secrets; see PowerShell and Microsoft Graph API for production patterns.

Build a reusable function that wraps Invoke-RestMethod with retry logic, logging, and timeout configuration. Test it with Pester mocks before deploying to production. For a deeper understanding of REST principles and JSON structure, API Basics: REST & JSON Explained covers the fundamentals that apply across all languages and tools.

Frequently Asked Questions

What is the difference between Invoke-RestMethod and Invoke-WebRequest in PowerShell?

Invoke-RestMethod automatically parses JSON and XML responses into PowerShell objects, returning the data directly. Invoke-WebRequest returns the full HTTP response with status code, headers, and raw content as a string, which you parse manually with ConvertFrom-Json. Use Invoke-RestMethod for API calls where you only need the data. Use Invoke-WebRequest when you need to inspect headers, handle non-standard status codes, or work with HTML content.

How do I pass a bearer token to a REST API in PowerShell?

Add an Authorization header with the format Bearer <token> using the -Headers parameter: $headers = @{ 'Authorization' = "Bearer $token" }, then Invoke-RestMethod -Uri $url -Headers $headers. Store the token in an environment variable or secret manager, never hardcode it. Most modern APIs including Microsoft Graph, GitHub, and Azure services use this pattern. The token must be valid and not expired or you'll get a 401 Unauthorized error.

How do I send JSON data in a POST request with PowerShell?

Build a hashtable with your data, convert it to JSON with ConvertTo-Json, and pass it to -Body with -ContentType 'application/json': $body = @{ name = 'value' } | ConvertTo-Json, then Invoke-RestMethod -Uri $url -Method Post -Body $body -ContentType 'application/json' -Headers $headers. For nested objects, add -Depth to ConvertTo-Json to avoid truncation. PUT requests use the same pattern with -Method Put.

How do I handle pagination when calling REST APIs from PowerShell?

Check the API response for a next or next_page field and loop until it's null: do { $response = Invoke-RestMethod -Uri $url; $allData += $response.data; $url = $response.next } while ($url). Some APIs use the Link HTTP header with rel="next"; extract it with a regex. Always check rate limit headers (X-RateLimit-Remaining) inside the loop and sleep if you're approaching the limit to avoid 429 or 403 errors.

How do I catch and log HTTP errors in PowerShell API scripts?

Wrap Invoke-RestMethod in a try-catch block. Catch [Microsoft.PowerShell.Commands.HttpResponseException] in PowerShell 7 or [System.Net.WebException] in PowerShell 5.1. Extract the status code from $_.Exception.Response.StatusCode.value__ and the error body from the response stream with StreamReader, then parse it as JSON. Use Write-Error for failures and Write-Verbose for request details. Add retry logic for 5xx errors but not 4xx client errors.

Share this article:
Thomas Ellison
About the Author

Thomas Ellison

Database Administration, Applied Mathematics, Technical Education, Documentation

Thomas Ellison is a database administrator and mathematics teacher with a strong focus on structured thinking, data integrity, and reliable system design.

He has extensive experience managing database systems in professional environments, ensuring performance, consistency, and long-term maintainability. His work includ...

Database Administration SQL Data Modeling Performance Optimization Mathematical Foundations

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.