API Interactions (REST APIs)
Note
Learn how to interact with REST APIs using PowerShell: making HTTP requests, handling authentication, working with JSON, and building practical API integrations.
Overview
REST APIs (Representational State Transfer Application Programming Interfaces) allow programs to communicate over HTTP. PowerShell provides powerful cmdlets for making API requests, processing responses, and automating API-based workflows. Whether you're pulling data from GitHub, sending alerts to Slack, or managing cloud resources, API interactions are essential for modern PowerShell scripting.
Common use cases:
- Fetching data from web services
- Automating cloud infrastructure (Azure, AWS, etc.)
- Integrating with collaboration tools (Slack, Teams, Jira)
- Monitoring and alerting systems
- CI/CD pipeline automation
Basic Concepts
What is a REST API?
A REST API uses HTTP methods to perform operations on resources:
| HTTP Method | Purpose | PowerShell Equivalent |
|---|---|---|
GET | Retrieve data | Read from server |
POST | Create new resource | Create on server |
PUT | Update entire resource | Update on server |
PATCH | Partial update | Modify on server |
DELETE | Remove resource | Delete from server |
Invoke-RestMethod vs Invoke-WebRequest
Key difference: Choose based on what you need
# Invoke-RestMethod - Returns parsed content (objects)
$data = Invoke-RestMethod -Uri "https://api.github.com/users/octocat"
$data.login # Direct property access
# Invoke-WebRequest - Returns full HTTP response
$response = Invoke-WebRequest -Uri "https://api.github.com/users/octocat"
$response.StatusCode # 200
$response.Headers # HTTP headers
$response.Content # Raw content (string)
When to Use Each
- Use
Invoke-RestMethod: When you just want the data (most common) - Use
Invoke-WebRequest: When you need HTTP headers, status codes, or raw response
Basic Syntax
Simple GET Request
# Basic GET request
$response = Invoke-RestMethod -Uri "https://api.example.com/users"
# With parameters in URL
$userId = 123
$response = Invoke-RestMethod -Uri "https://api.example.com/users/$userId"
# With query string parameters
$params = @{
page = 1
limit = 50
}
$response = Invoke-RestMethod -Uri "https://api.example.com/users" -Body $params
# Result: https://api.example.com/users?page=1&limit=50
POST Request with JSON Body
# Create data as hashtable
$newUser = @{
name = "John Doe"
email = "jdoe@example.com"
active = $true
}
# Convert to JSON and send
$jsonBody = $newUser | ConvertTo-Json
$response = Invoke-RestMethod -Uri "https://api.example.com/users" `
-Method Post `
-Body $jsonBody `
-ContentType "application/json"
PUT Request (Update)
# Update existing resource
$updateData = @{
name = "Jane Doe"
email = "jane.doe@example.com"
}
$response = Invoke-RestMethod -Uri "https://api.example.com/users/123" `
-Method Put `
-Body ($updateData | ConvertTo-Json) `
-ContentType "application/json"
DELETE Request
# Delete a resource
Invoke-RestMethod -Uri "https://api.example.com/users/123" -Method Delete
# Check status code with Invoke-WebRequest
$response = Invoke-WebRequest -Uri "https://api.example.com/users/123" -Method Delete
if ($response.StatusCode -eq 204) {
Write-Output "User deleted successfully"
}
Authentication
API Key in Headers
# Simple API key authentication
$headers = @{
"X-API-Key" = "your-api-key-here"
}
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
Bearer Token (OAuth)
# Bearer token authentication
$token = "your-access-token"
$headers = @{
"Authorization" = "Bearer $token"
}
$response = Invoke-RestMethod -Uri "https://api.example.com/protected" -Headers $headers
Basic Authentication
# Method 1: Using -Credential parameter
$credential = Get-Credential
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Credential $credential
# Method 2: Manual Basic Auth header
$username = "user"
$password = "pass"
$base64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${username}:${password}"))
$headers = @{
"Authorization" = "Basic $base64"
}
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
Custom Headers
# Multiple custom headers
$headers = @{
"X-API-Key" = "abc123"
"X-Request-ID" = [guid]::NewGuid().ToString()
"Accept" = "application/json"
"User-Agent" = "PowerShell Script v1.0"
}
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
Common Use Cases
Use Case 1: Fetch Data from Public API
What it does: Retrieve information from a public API without authentication
# Get random user data
$user = Invoke-RestMethod -Uri "https://randomuser.me/api/"
Write-Output "Name: $($user.results[0].name.first) $($user.results[0].name.last)"
Write-Output "Email: $($user.results[0].email)"
# Get current weather (requires free API key)
$city = "London"
$apiKey = "your-key"
$weather = Invoke-RestMethod -Uri "https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey"
Write-Output "Temperature: $($weather.main.temp)K"
Use Case 2: GitHub API Integration
What it does: Interact with GitHub repositories
# Get repository information
$repo = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/PowerShell"
Write-Output "Stars: $($repo.stargazers_count)"
Write-Output "Forks: $($repo.forks_count)"
Write-Output "Open Issues: $($repo.open_issues_count)"
# List recent commits
$commits = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/PowerShell/commits?per_page=5"
foreach ($commit in $commits) {
Write-Output "$($commit.commit.author.date): $($commit.commit.message)"
}
Use Case 3: Create Resource via API
What it does: POST data to create new resources
# Create new GitHub issue (requires auth token)
$token = "ghp_yourPersonalAccessToken"
$headers = @{
"Authorization" = "Bearer $token"
"Accept" = "application/vnd.github.v3+json"
}
$issue = @{
title = "Bug Report: Application crashes on startup"
body = "## Description\nThe application crashes when launched.\n\n## Steps to Reproduce\n1. Open app\n2. Crash occurs"
labels = @("bug", "high-priority")
} | ConvertTo-Json
$response = Invoke-RestMethod `
-Uri "https://api.github.com/repos/username/repo/issues" `
-Method Post `
-Headers $headers `
-Body $issue `
-ContentType "application/json"
Write-Output "Issue created: #$($response.number)"
Use Case 4: Query String Parameters
What it does: Send parameters as query string
# Search GitHub repositories
$params = @{
q = "language:powershell"
sort = "stars"
order = "desc"
per_page = 10
}
$results = Invoke-RestMethod -Uri "https://api.github.com/search/repositories" -Body $params
foreach ($repo in $results.items) {
Write-Output "$($repo.full_name) - Stars: $($repo.stargazers_count)"
}
Use Case 5: Pagination Handling
What it does: Retrieve all pages of results
# GitHub API pagination example
$allRepos = @()
$page = 1
$perPage = 100
do {
$repos = Invoke-RestMethod -Uri "https://api.github.com/users/octocat/repos?page=$page&per_page=$perPage"
$allRepos += $repos
$page++
} while ($repos.Count -eq $perPage)
Write-Output "Total repositories: $($allRepos.Count)"
Real-World Examples
Example: Slack Notification Bot
Scenario: Send alerts to Slack channel
function Send-SlackAlert {
param(
[string]$WebhookUrl,
[string]$Message,
[string]$Channel = "#alerts"
)
$payload = @{
channel = $Channel
text = $Message
username = "PowerShell Bot"
icon_emoji = ":robot_face:"
} | ConvertTo-Json
try {
Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $payload -ContentType "application/json"
Write-Output "Alert sent to Slack"
} catch {
Write-Error "Failed to send Slack alert: $_"
}
}
# Usage
$webhook = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
Send-SlackAlert -WebhookUrl $webhook -Message "Deployment completed successfully!"
Example: Monitor Website Status
Scenario: Check if websites are up and log results
$websites = @(
"https://www.google.com"
"https://www.github.com"
"https://www.microsoft.com"
)
$results = foreach ($site in $websites) {
try {
$response = Invoke-WebRequest -Uri $site -TimeoutSec 10 -UseBasicParsing
[PSCustomObject]@{
Website = $site
Status = "Up"
StatusCode = $response.StatusCode
ResponseTime = $response.Headers.'X-Response-Time'
Timestamp = Get-Date
}
} catch {
[PSCustomObject]@{
Website = $site
Status = "Down"
StatusCode = $null
ResponseTime = $null
Timestamp = Get-Date
}
}
}
$results | Format-Table -AutoSize
$results | Export-Csv -Path "C:\Logs\website-status.csv" -NoTypeInformation -Append
Example: Azure DevOps Integration
Scenario: Get build status from Azure DevOps
$organization = "your-org"
$project = "your-project"
$pat = "your-personal-access-token"
# Encode PAT for basic auth
$base64Pat = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
$headers = @{
"Authorization" = "Basic $base64Pat"
}
# Get recent builds
$uri = "https://dev.azure.com/$organization/$project/_apis/build/builds?api-version=6.0&`$top=10"
$builds = Invoke-RestMethod -Uri $uri -Headers $headers
foreach ($build in $builds.value) {
$status = if ($build.result -eq "succeeded") { "✅" } else { "❌" }
Write-Output "$status Build #$($build.buildNumber) - $($build.definition.name) - $($build.status)"
}
Example: REST API CRUD Operations
Scenario: Complete create, read, update, delete workflow
$baseUri = "https://jsonplaceholder.typicode.com"
# CREATE - POST new post
$newPost = @{
title = "My PowerShell Post"
body = "This post was created via PowerShell"
userId = 1
} | ConvertTo-Json
$created = Invoke-RestMethod -Uri "$baseUri/posts" -Method Post -Body $newPost -ContentType "application/json"
Write-Output "Created post ID: $($created.id)"
# READ - GET post
$post = Invoke-RestMethod -Uri "$baseUri/posts/$($created.id)"
Write-Output "Title: $($post.title)"
# UPDATE - PUT (replace entire resource)
$updated = @{
id = $created.id
title = "Updated Title"
body = "Updated body content"
userId = 1
} | ConvertTo-Json
$result = Invoke-RestMethod -Uri "$baseUri/posts/$($created.id)" -Method Put -Body $updated -ContentType "application/json"
Write-Output "Updated title: $($result.title)"
# DELETE - Remove resource
Invoke-RestMethod -Uri "$baseUri/posts/$($created.id)" -Method Delete
Write-Output "Post deleted"
Example: Handling Rate Limits
Scenario: Respect API rate limits with retry logic
function Invoke-APIWithRateLimit {
param(
[string]$Uri,
[hashtable]$Headers,
[int]$MaxRetries = 3
)
$retries = 0
while ($retries -lt $MaxRetries) {
try {
$response = Invoke-WebRequest -Uri $Uri -Headers $Headers
# Check rate limit headers
$remaining = $response.Headers.'X-RateLimit-Remaining'
$reset = $response.Headers.'X-RateLimit-Reset'
if ($remaining -and $remaining -lt 10) {
$resetTime = [DateTimeOffset]::FromUnixTimeSeconds($reset).DateTime
$waitTime = ($resetTime - (Get-Date)).TotalSeconds
Write-Warning "Rate limit low. Waiting $waitTime seconds..."
Start-Sleep -Seconds $waitTime
}
return $response.Content | ConvertFrom-Json
} catch {
if ($_.Exception.Response.StatusCode -eq 429) {
$retries++
$waitTime = [math]::Pow(2, $retries) * 5 # Exponential backoff
Write-Warning "Rate limited. Retry $retries/$MaxRetries after $waitTime seconds..."
Start-Sleep -Seconds $waitTime
} else {
throw
}
}
}
throw "Max retries exceeded due to rate limiting"
}
Error Handling
Try-Catch for API Calls
try {
$response = Invoke-RestMethod -Uri "https://api.example.com/users/999999"
Write-Output "User found: $($response.name)"
} catch {
$statusCode = $_.Exception.Response.StatusCode.value__
switch ($statusCode) {
404 { Write-Error "User not found" }
401 { Write-Error "Unauthorized - check your credentials" }
403 { Write-Error "Forbidden - insufficient permissions" }
429 { Write-Error "Rate limit exceeded - try again later" }
500 { Write-Error "Server error - API may be down" }
default { Write-Error "API error: $statusCode - $($_.Exception.Message)" }
}
}
Checking Response Status
# Use Invoke-WebRequest when you need status codes
$response = Invoke-WebRequest -Uri "https://api.example.com/data" -Method Get
if ($response.StatusCode -eq 200) {
$data = $response.Content | ConvertFrom-Json
Write-Output "Success: Retrieved $($data.Count) items"
} elseif ($response.StatusCode -eq 204) {
Write-Output "Success: No content returned"
} else {
Write-Warning "Unexpected status: $($response.StatusCode)"
}
Important Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
-Uri | String | The API endpoint URL | -Uri "https://api.example.com/data" |
-Method | String | HTTP method (Get, Post, Put, Delete, Patch) | -Method Post |
-Body | String/Hashtable | Request body (often JSON) | -Body $jsonData |
-Headers | Hashtable | HTTP headers (auth, content-type, etc.) | -Headers @{"Authorization"="Bearer token"} |
-ContentType | String | Content type of request body | -ContentType "application/json" |
-TimeoutSec | Integer | Request timeout in seconds | -TimeoutSec 30 |
-Credential | PSCredential | Credentials for basic auth | -Credential $cred |
-UseBasicParsing | Switch | Parse HTML without IE engine | -UseBasicParsing |
-OutFile | String | Save response to file | -OutFile "response.json" |
Common Patterns
# Pattern 1: Simple GET with authentication
$headers = @{"Authorization" = "Bearer $token"}
$data = Invoke-RestMethod -Uri "https://api.example.com/resource" -Headers $headers
# Pattern 2: POST with JSON body
$body = @{key="value"} | ConvertTo-Json
$result = Invoke-RestMethod -Uri "https://api.example.com/resource" -Method Post -Body $body -ContentType "application/json"
# Pattern 3: Download file from API
Invoke-RestMethod -Uri "https://api.example.com/download/file.zip" -OutFile "C:\Downloads\file.zip"
# Pattern 4: Custom User-Agent
$headers = @{"User-Agent" = "MyScript/1.0 (PowerShell)"}
$data = Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
# Pattern 5: Handling large responses
$response = Invoke-WebRequest -Uri "https://api.example.com/large-data"
$response.Content | Out-File -FilePath "C:\Temp\large-data.json"
Tips & Tricks
Use Splatting for Complex Requests
# BAD - Hard to read
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Method Post -Headers @{"Authorization"="Bearer token"} -Body ($data | ConvertTo-Json) -ContentType "application/json"
# GOOD - Use splatting
$params = @{
Uri = "https://api.example.com/data"
Method = "Post"
Headers = @{"Authorization" = "Bearer $token"}
Body = $data | ConvertTo-Json
ContentType = "application/json"
}
$response = Invoke-RestMethod @params
Store API Keys Securely
# BAD - Hardcoded API key in script
$apiKey = "sk_live_abc123xyz"
# GOOD - Use environment variable
$apiKey = $env:API_KEY
# BETTER - Use secure string or credential manager
$secureKey = Get-Content "C:\secure\api-key.txt" | ConvertTo-SecureString
$ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureKey)
$apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ptr)
Debug API Calls with -Verbose
Test APIs with JSONPlaceholder
Always Use -ContentType with POST/PUT
# BAD - Missing ContentType, API might reject request
$body = @{name="test"} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.example.com/users" -Method Post -Body $body
# GOOD - Explicit ContentType
$body = @{name="test"} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.example.com/users" -Method Post -Body $body -ContentType "application/json"
Handle Pagination in Loops
# BAD - Only gets first page
$data = Invoke-RestMethod -Uri "https://api.example.com/items"
# GOOD - Loop through all pages
$allData = @()
$page = 1
do {
$response = Invoke-RestMethod -Uri "https://api.example.com/items?page=$page"
$allData += $response.items
$page++
} while ($response.items.Count -gt 0)
Escape Special Characters in URLs
# BAD - Spaces and special chars break URL
$search = "hello world"
$url = "https://api.example.com/search?q=$search" # Won't work!
# GOOD - URL encode parameters (cross-platform)
$search = [Uri]::EscapeDataString("hello world")
$url = "https://api.example.com/search?q=$search"
# Result: https://api.example.com/search?q=hello%20world
# BETTER - Use -Body parameter (auto-encodes)
$params = @{q = "hello world"}
Invoke-RestMethod -Uri "https://api.example.com/search" -Body $params
Check API Response Formats
# Some APIs return arrays, some return objects with array properties
# Array response
$users = Invoke-RestMethod -Uri "https://api.example.com/users"
$users.Count # Works if array
# Object with array property
$response = Invoke-RestMethod -Uri "https://api.example.com/users"
$response.data.Count # Data is in 'data' property
$response.pagination.total # Total count in metadata
Related Topics
- Working with JSON - JSON conversion and parsing
- String Manipulation - Text processing
- Arrays & Collections - Hashtables for API request bodies
- Error Handling - Handling API failures
- Variables & Data Types - Working with API response data
- One-Liners - Quick API request examples
Additional Resources
- Microsoft Docs: Invoke-RestMethod
- Microsoft Docs: Invoke-WebRequest
- JSONPlaceholder - Free fake REST API for testing
- ReqBin - Online REST API testing tool
- HTTP Status Codes - Complete reference