Skip to content

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

# See detailed request/response info
$response = Invoke-RestMethod -Uri "https://api.example.com/data" -Verbose

# Or use Invoke-WebRequest to inspect response
$response = Invoke-WebRequest -Uri "https://api.example.com/data"
$response.StatusCode
$response.Headers
$response.Content

Test APIs with JSONPlaceholder

# Free fake API for testing
# No authentication required!
$posts = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts"
$users = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/users"
$comments = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/comments"

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

Additional Resources