Do you often access application programming interfaces (APIs) using PowerShell? Maybe you want to but don’t know where to start? Whether you’re a PowerShell pro or just starting, this tutorial has you covered with a built-in PowerShell cmdlet that interacts with APIs called Invoke-RestMethod
.
In this article, you’ll learn many different ways to work with representational state transfer (REST) APIs from using GET and POST requests, covering authentication, how to download files, and more!
Invoke-RestMethod in a Nutshell
When you need to retrieve or send data to a REST API, you need a client. In the PowerShell world, that client is the Invoke-RestMethod
cmdlet. This cmdlet sends HTTP requests using various HTTP methods to REST API endpoints.
HTTP methods then instruct REST APIs to carry out various actions to be performed on a resource.
The official HTTP methods are GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, and PATCH, although some APIs may implement custom methods.
The Invoke-RestMethod
cmdlet supports all HTTP methods, including authentication, sending different HTTP headers, HTTP bodies, and also automatically translates JSON and XML responses to PowerShell objects. The Invoke-RestMethod
cmdlet is the PowerShell cmdlet to interact with REST APIs!
Prerequisites
If you’d like to follow along with the many demos in this tutorial, be sure that you have:
- PowerShell 7.0 or later installed. This tutorial uses a Windows 10 machine and PowerShell 7.1.
Without further ado, open your PowerShell console and/or code editor and let’s get started!
Announcing a Free LIVE training – Starting your PowerShell Journey – presented by Johan Arwidmark. Understand how PowerShell skills enhance your IT career, learn where to start with PowerShell, build your first scripts, and ask Johan questions directly in a live training environment.
Retrieving Data via a Simple GET request
Let’s start things off with the simplest example out there; querying a REST API with a GET request. Invoke-RestMethod
can do a lot, but you need to understand the basics first.
To send a simple GET request to a REST API endpoint, you’ll only need one parameter, Uri
. The Uri
parameter is what tells Invoke-RestMethod
where the endpoint is.
For example, run the command below. This command queries the JSONPlaceholder APIs posts
endpoint and returns a list of post
resources.
The JSONPlaceholder site offers a free fake API for testing, which is used to demonstrate real examples of queries with the
Invoke-RestMethod
command.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts"
When the REST endpoint https://jsonplaceholder.typicode.com/posts returns data, it doesn’t return it in nice PowerShell objects, as you see above. Instead, it returns data in JSON. Invoke-RestMethod
automatically converted the JSON to PowerShell objects for you.
You can see below that PowerShell converted the output to the PSCustomObject
type by looking at a single item in the PowerShell array and running the GetType()
method on it.
# Store the API GET response in a variable ($Posts).
$Posts = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts"
# Run the GetType() method against the first item in the array, identified by its index of 0.
$Posts[0].GetType()
Authenticating to an API
In the previous section, you queried a public REST API using the GET method. The API didn’t require any authentication. Much of the time, though, you must authenticate to a REST API somehow.
Two of the most common ways to authenticate to a REST API is using Basic (username/password) or Bearer (token) authentication. To differentiate between these two wildly different authentication schemes requires using an Authorization HTTP header when sending the request.
Let’s now cover how you can use Invoke-RestMethod
to send HTTP headers (especially the Authorization HTTP header) to REST endpoints.
The
Invoke-RestMethod
abstracts away a lot of the tedium to sending HTTP requests. Even though you must provide an Authorization header in an HTTP request, you’ll see no references to “headers” in this example. Abstracting away concepts like this is common with theInvoke-RestMethod
cmdlet.
Using a Username and Password with Basic Authentication
The simplest way to authenticate to a REST endpoint is using a username and password. To capture that username and password, you must pass a PSCredential object to the endpoint that contains the username and password.
First, create the PSCredential object containing the username and password.
# This will prompt for credentials and store them in a PSCredential object.
$Cred = Get-Credential
Once you have a PSCredential object stored in a variable, pass the required URI to the command but this time add the Authentication
and Credential
parameter.
Setting the Authentication
parameter sends an authorization HTTP header containing the word Basic
, followed by a base64
encoded username:password
string like Authorization: Basic ZGVtbzpwQDU1dzByZA==
.
The Credential
parameter accepts the PSCredential you created earlier.
The below example and many more in this tutorial use a concept called PowerShell splatting that allows you to define parameters in a hashtable and then pass to the command. Learn more about splatting in the ATA post PowerShell Splatting: What is it and how does it work?
# Send a GET request including Basic authentication.
$Params = @{
Uri = "https://jsonplaceholder.typicode.com/posts"
Authentication = "Basic"
Credential = $Cred
}
Invoke-RestMethod @Params
If you use the
Credential
orAuthentication
parameter option with aUri
that does not begin with https://,Invoke-RestMethod
will return an error for security reasons. The override this default behavior, use theAllowUnencryptedAuthentication
parameter at your own risk.
Using an API/OAuth Token with Bearer Authentication
Basic username and password authentication are OK, but it’s not great. Credentials are simply encoded as base64 (not encrypted) which opens up security issues. To address this, APIs usually implement a token authentication system or Bearer/OAuth authentication.
To authenticate to a REST API with an OAuth token:
1. Obtain the OAuth token from your API. How this token is obtained will depend on your API provider.
2. Next, convert your token string into a secure string with the ConvertTo-SecureString
cmdlet, as shown below. The Invoke-RestMethod
requires the token to be a secure string.
$Token = "123h1v23yt2egv1e1e1b2ei1ube2iu12be" | ConvertTo-SecureString -AsPlainText -Force
3. Finally, define and pass the Uri
, Authentication
type, and Token
to the Invoke-RestMethod
cmdlet. Invoke-RestMethod
will then call the URI provided and add the token to the Authorization HTTP header.
The
Authentication
parameter argumentOAuth
is an alias forBearer
. You can use both of these parameter values interchangeably.
# Send a GET request including bearer authentication.
$Params = @{
Uri = "https://jsonplaceholder.typicode.com/posts"
Authentication = "Bearer"
Token = $Token
}
Invoke-RestMethod @Params
Retrieving Data with Using Query Parameters
Typically, sending a GET request to a REST API is more involved than just a simple, generic request to an endpoint. Instead, you need to pass parameters to specify exactly what you need from the API; you need to pass HTTP query parameters.
To send query parameters with Invoke-RestMethod
, you have two options. You can either directly append the parameters to the URI, as shown below, which passes a userId
of 1
and an id
of 8
.
https://jsonplaceholder.typicode.com/posts?userId=1&id=8
Or, you could define the parameters in the HTTP body using the Body
parameter as a hashtable. Let’s cover how to pass parameters to an endpoint using the Body
parameter.
Create a hashtable containing the query parameter key/value pairs, as follows.
$Body = @{
userId = 1
id = 8
}
Finally, provide the $Body
variable to the Body
parameter, as shown below.
You can specify the
Method
parameter using a value ofGET
or exclude theMethod
parameter orInvoke-RestMethod
to default to the value.
$Params = @{
Method = "Get"
Uri = "https://jsonplaceholder.typicode.com/posts"
Body = $Body
}
Invoke-RestMethod @Params
You can now see below that the endpoint only returns the post item you’re looking for.
Sending Data to an API with the POST HTTP Method
In the previous examples, you were querying data from a REST API or using HTTP GET requests. You were reading the data it sent back, but reading is only half the story with many REST APIs. REST APIs must support a full CRUD model so you can interact with the service.
When you need to make changes to a service providing an API, you won’t use a GET HTTP request; you’ll use a “writable” request like POST.
You’ll also typically need to pass an HTTP body with requests when using any “writable” HTTP method like PUT or PATCH.
Sending JSON Data in a POST Request
Using the previous REST API endpoint, let’s now create a new post item rather than just reading them.
1. First, create a hashtable including all of the attributes for the posts API endpoint. You’ll see below that the tutorial’s specific endpoint allows you to create a new post item with a title
, body
and userId
.
$Body = @{
title = "foo"
body = "bar"
userId = 1
}
2. Next, convert the hashtable represented in the $Body
variable to a JSON string storing it in a new variable $JsonBody
.
REST endpoints don’t know what a PowerShell hashtable is, and you must convert the object into a language that the REST API understands. Creating a hashtable first is optional. You could type up the JSON directly and skip this step if you wanted to.
$JsonBody = $Body | ConvertTo-Json
3. Finally, craft the required parameters and run Invoke-RestMethod
. Notice below that you must now use the Method
parameter with a value of Post
. Without using the Method
parameter, Invoke-RestMethod
defaults to sending a GET request.
Also, many REST APIs require you to specify the ContentType
indicating the HTTP Content-Type header the Body
is stored in. In this example, you must use application/json
.
# The ContentType will automatically be set to application/x-www-form-urlencoded for
# all POST requests, unless specified otherwise.
$Params = @{
Method = "Post"
Uri = "https://jsonplaceholder.typicode.com/posts"
Body = $JsonBody
ContentType = "application/json"
}
Invoke-RestMethod @Params
Notice below that the API returns a post item along with an id
for that new post.
Sending Form Data with Invoke-RestMethod
Some REST API endpoints may require you to submit data via the multipart/form-data
HTTP content type. To send a different content type with Invoke-RestMethod
is a bit easier than using JSON. Since PowerShell 6.1.0, you can now use the Form
parameter.
The Form
parameter provides a convenient way to add multipart/form-data
objects to a request without the need to use the .NET System.Net.Http.MultipartFormDataContent
class directly.
To send form data with Invoke-RestMethod
, first, create a hashtable with each item as before.
$Form = @{
title = "foo"
body = "bar"
userId = 1
}
Notice using the
Form
parameter; you don’t need to use theBody
parameter. Also, if you attempt to specify theContentType
and theForm
parameter together,Invoke-RestMethod
will ignore theContentType
parameter.
Finally, simply pass the hashtable to the Form
parameter, as shown below.
$Params = @{
Method = "Post"
Uri = "https://jsonplaceholder.typicode.com/posts"
Form = $Form
}
Invoke-RestMethod @Params
Following Relation Links
Rather than returning massive datasets in one go, APIs often return “pages” of data. For example, the GitHub Issues API returns 30 issues per page by default. Some APIs include links to the next (or previous, last, etc.) page of data the response to help navigate the dataset known as relation links.
To find relation links an API returns, you must inspect the HTTP response headers. One easy way to do that is to use the ResponseHeadersVariable
parameter. This parameter automatically creates a variable and stores the headers in a hashtable.
Let’s use the PowerShell GitHub repo’s issues as an example.
1. Make a GET request to the PowerShell GitHub repo’s issues endpoint, as shown below. Be sure to use the ResponseHeadersVariable
to create a variable. The below example uses the $Headers
variable.
# Issue GET request to GitHub issues API for the PowerShell project repo and store
# the response headers in a variable ($Headers).
Invoke-RestMethod -Uri "https://api.github.com/repos/powershell/powershell/issues" -ResponseHeadersVariable "Headers"
# Print the $Headers variable to the console.
$Headers
Notice below that the hashtable inside of the $Headers
variable has a key called Links
. This key contains the relation links for the response that indicates the data set is bigger than just this one response.
2. Next, follow the relation links using the FollowRelLink
parameter. This parameter automatically reads each of the relation links and issues a GET request for each of them.
The below code snippet is following each relation link up to three. In this example, the Invoke-RestMethod
cmdlet will stop querying for issues once it hits 90 (30 items per request) using the
$Params = @{
Uri = "https://api.github.com/repos/powershell/powershell/issues"
FollowRelLink = $true
MaximumFollowRelLink = 3
}
Invoke-RestMethod @Params
When using the FollowRelLink
parameter, Invoke-RestMethod
returns an array of objects (Object[]
). Each item in the array contains the response from one of the relation links, which could be another array of objects itself!
3. Re-run the previous example but this time check on the returned results from the initial query. You’ll see a count of only 3
, meaning three “pages.” But you’ll see that the first page of items ($Results[0]
) contains 30 items.
$Params = @{
Uri = "https://api.github.com/repos/powershell/powershell/issues"
FollowRelLink = $true
MaximumFollowRelLink = 3
}
# Store the three pages of results in the $Results variable.
$Results = Invoke-RestMethod @Params
# Check that $Results contains three items (pages of issues from the GitHub issues API).
$Results.Count
# Check that the first item in the $Results array contains the first page of thirty issues.
$Results[0].Count
4. Finally, iterate over each item in the $Results
variable using a foreach
loop. You’ll see below you’ll have to iterate over each page with a foreach
loop. Then, for each page, iterate over all of the items in that page requiring a nested loop.
# This might be different depending on the data structure of the API you are using.
# 1) $Results.ForEach({}) - this loops through each page in the $Results array.
# 2) $_.ForEach({}) - this loops through each item in the current page.
# 3) $_ - this simply returns each item to the pipeline.
$AllResults = @( $Results.ForEach({ $_.ForEach({ $_ }) }) )
# Check that the $AllResults variable contains all ninety items.
$AllResults.Count
Maintaining Session Information
When working with APIs, it’s often useful to store information related to a previous request such as headers, credentials, proxy details, and cookies, to re-use in subsequent requests. All of this information is stored in a session.
The Invoke-RestMethod
can leverage sessions by storing the session using the SessionVariable
parameter and then referencing that session using the WebSession
parameter.
To demonstrate, call the posts endpoint again and this time use the SessionVariable
parameter, as shown below. In this example, Invoke-RestMethod
will create a variable called MySession
.
Remember, the session object isn’t a persistent connection. A session is simply an object that contains information about the request.
# Invoke the request storing the session as MySession.
# The SessionVariable value shouldn't include a dollar sign ($).
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -SessionVariable "MySession"
# Print the session object to the console.
$MySession
Now, re-use the session information by calling Invoke-RestMethod
with the WebSession
parameter. As you can see in the following example, all previous session values are passed via the $MySession
variable in the new request.
# Invoke the request using the session information stored in the $MySession variable.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -WebSession $MySession
Overriding Session Values
A session contains various information about the request. If you want to re-use the session but change a value, you can override it.
Perhaps, you’d like to re-use the session previously created but now authenticate with a username and password. Let’s first see what the before situation looks like.
Notice below that the $MySession
object does not contain any value for the Credentials
property. But, after invoking Invoke-RestMethod
again using the Credential
parameter, the REST endpoint receives the credential even though it wasn’t in the session.
# Print the $MySession variable to the console to demonstrate that the Credentials
# property is empty.
$MySession
# Override the session value by specifying the Credential parameter.
# In this example you will be prompted for the username and password.
$Params = @{
Uri = "https://jsonplaceholder.typicode.com/posts"
WebSession = $MySession
Credential = (Get-Credential)
}
Invoke-RestMethod @Params
The property in the saved session is named
Credentials
, but the parameter name isCredential
. The names will not always match.
Saving the Response Body to a File
Sometimes it will be necessary to save the response from a request to a file. To do that, use the OutFile
parameter.
Run Invoke-RestMethod
again to query the tutorial’s testing endpoint but this time use the OutFile
parameter and provide a file path.
You’ll see below that Invoke-RestMethod
queries the endpoint, returns the response in JSON format, and then saves the raw JSON into the .\my-posts.json file.
You can also use the
PassThru
parameter to return the response to the console and save a file with the response at once.
# Save post items to my-posts.json in the current directory.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -OutFile "my-posts.json"
# Print the contents of the JSON file to the console.
# ".\" in this command refers to the current working directory in your terminal session.
Get-Content -Path ".\my-posts.json"
Once you have the response saved as JSON in a file, you can parse it for information as you’d like. Below you’ll find a good example of finding a post with a specific ID.
# Save the response to "my-posts.json" and also in the $Posts variable using PassThru.
$Posts = Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -OutFile "my-posts.json" -PassThru
# Filter posts with 1 as the userId into a new variable ($User1Posts).
$User1Posts = $Posts.Where({$_.userId -eq 1})
# Import all posts from the "my-posts.json" file and store them in the $AllUserPosts variable.
$AllUserPosts = Get-Content -Path ".\my-posts.json" | ConvertFrom-Json
# Print the count of both variables to the console to demonstrate that they are different.
$User1Posts.Count
$AllUserPosts.Count
Working with SSL and Certificates
Throughout this tutorial, you’ve only been working with HTTP. HTTPS and SSL haven’t come into the picture. But that doesn’t mean Invoke-RestMethod
won’t work with SSL. In fact, it can manage just about anything you need.
Skipping Certificate Validation
By default, Invoke-RestMethod
validates any SSL site’s certificate to ensure it’s not expired, revoked, or the trust chain is intact. Although this behavior is a security feature you should leave on, there are times, like when testing, you need to disable it.
To skip certificate validation, use the SkipCertificateCheck
parameter. This parameter removes all certificate validation Invoke-RestMethod
typically runs.
Specifying a Client Certificate for a Request
If you need to specify a client certificate for a particular request, use Invoke-RestMethod
‘s Certificate
parameter. This parameter takes an X509Certificate
object as its value which you can retrieve using the Get-PfxCertificate
command or the Get-ChildItem
command from within the Cert:
PSDrive.
For example, the following command uses a certificate from the Cert:
drive to make a request to the JSONPlaceholder APIs posts
endpoint.
# Change location into your personal certificate store.
Set-Location "Cert:\CurrentUser\My\"
# Store the certificate with the thumbprint DDE2EC6DBFF56EE9C375A6073C97188ABAA4F5E4 in a variable ($Cert).
$Cert = Get-ChildItem | Where-Object {$_.Thumbprint -eq "DDE2EC6DBFF56EE9C375A6073C97188ABAA4F5E4"}
# Invoke the command using the client certificate.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -Certificate $Cert
Restricting SSL/TLS Protocols
By default, all SSL/TLS protocols supported by your system are allowed. But, if you need to restrict a request to a specific protocol version(s), use the SslProtocol
parameter.
Using the SslProtocol
, you can specifically call a URI with a version of TLS from v1, 1.1, 1.2 and 1.3 as an array.
# Restrict the request to only allow SSL/TLS 1.2 and 1.3 protocol versions.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -SslProtocol @("Tls12", "Tls13")
On non-Windows platforms, you may not have
Tls
orTls12
as an option. Support forTls13
is not available on all operating systems and will need to be verified on a per operating system basis.Tls13
is only available in PowerShell 7.1+.
Other Interesting Features
To wrap up this tutorial, let’s finish off with some useful parameters but don’t necessarily need an instruction section.
Using a Proxy Server
Corporate environments often use proxy servers to manage internet access. To force Invoke-RestMethod
to proxy its request through a proxy, use the Proxy
parameter.
If you need to authenticate to the proxy, either supply a PSCredential
object to the ProxyCredential
parameter or use the switch parameter ProxyUseDefaultCredentials
to use the currently logged-on user’s credentials.
# Invoke request using proxy server <http://10.0.10.1:8080> and the current user's credentials.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -Proxy "http://10.0.10.1:8080" -ProxyUseDefaultCredentials
Skipping Checks and Validation
Using these parameters can expose you to potential security risks. You’ve been warned!
The Invoke-RestMethod
cmdlet has many different checks it does under the hood. If you’d prefer to disable these checks for some reason, you can do with with a few parameters.
- SkipHeaderValidation – Disable validation for values passed to the
ContentType
,Headers
, andUserAgent
parameters. - SkipHttpErrorCheck – Any errors will be ignored. The error will be written to the pipeline before processing continues.
- StatusCodeVariable – When using
SkipHttpErrorCheck
, you might need to check the HTTP response status code to identify success or failure messages. TheStatusCodeVariable
parameter will assign the status code integer value to a variable for this purpose.
Disabling Keep Alive
TCP Keep Alive is a handy network-level feature that allows you to create a persistent connection to a remote server (if the server supports it). By default, Invoke-RestMethod
does not use Keep Alive.
If you’d like to use Keep Alive, potentially reducing the CPU and memory usage of the remote server, set the DisableKeepAlive
parameter to $false
.
Invoke-RestMethod -Uri "https://jsonplaceholder.typicode.com/posts" -DisableKeepAlive $false
Changing the Encoding Type
Whenever Invoke-RestMethod
sends a request to a remote endpoint, it encodes the request using a transfer-encoding header. By default, Invoke-RestMethod
and the server negotiate this encoding method, but you can explicitly define an encoding type with the TransferEncoding
parameter.
If you’d like to change the encoding type, you may do using:
- Chunked
- Compress
- Deflate
- GZip
- Identity
Announcing a Free LIVE training – Starting your PowerShell Journey – presented by Johan Arwidmark. Understand how PowerShell skills enhance your IT career, learn where to start with PowerShell, build your first scripts, and ask Johan questions directly in a live training environment.
Conclusion
In this tutorial, you’ve learned how Invoke-RestMethod
makes interacting with REST APIs much easier than with standard web requests. You’ve looked at parameters for authentication, sending data in the body of a request, maintaining session state, downloading files, and much more.
Now that you’re up to speed on Invoke-RestMethod
and working with REST APIs, what REST API will you try this handy cmdlet on?