Note: If you read this post and grab some examples from it, make sure you read to the bottom. I wrote my functions using a GitHub-depreciated JSON object up until the final function. You have been warned.
I am working with the GitHub REST API currently and I learned something new. Why not share it and create a resource for myself for the moment in time when I need a refresh.
Unauthenticated API requests to GitHub are limited to 60 per hour. I did some of my own checks, but there is documentation around this. At home, which is where I work these days, the private/NAT’ed IPs on two different computers, behind my public IP address, were two different devices to GitHub. This was not a huge surprise, but good to know, nonetheless.
Let’s take the GitHub rate limit API for a spin. This API is not rate-limited, which means you can call it as many times as you would like in an hour in an unauthenticated manner. GitHub writes, “Accessing this endpoint does not count against your REST API rate limit.” In the below example, we assign a $Uri
and $Header
variable and use those as a part of our Invoke-RestMethod
command, which stores the results in the $Json
variable.
$Uri = 'https://api.github.com/rate_limit' $Headers = @{Headers = 'application/vnd.github.v3+json'} $Json = Invoke-RestMethod -Uri $Uri -Headers $Headers | ConvertTo-Json $Json
Once that is done, we output the contents stored in the $Json
variable. Let’s chat about some of the data returned in this JSON object. Beneath resources
and then core
, we have limit
, remaining
, and used
. Limit
is how many unauthenticated requests we have (per hour), remaining
is how many of 60 we have left in the hour, and used
, is how many API calls we have already used this hour. Therefore if we had 59 remaining, we would have 1 used. Toward the bottom, we have rate
. The content stored in this object is identical to what is stored in resources.core
.
{ "resources": { "core": { "limit": 60, "remaining": 60, "reset": 1646979987, "used": 0, "resource": "core" }, "graphql": { "limit": 0, "remaining": 0, "reset": 1646979987, "used": 0, "resource": "graphql" }, "integration_manifest": { "limit": 5000, "remaining": 5000, "reset": 1646979987, "used": 0, "resource": "integration_manifest" }, "search": { "limit": 10, "remaining": 10, "reset": 1646976447, "used": 0, "resource": "search" } }, "rate": { "limit": 60, "remaining": 60, "reset": 1646979987, "used": 0, "resource": "core" } }
In the above, Invoke-RestMethod
command, we piped the output to ConvertTo-Json
in order to see the JSON object. Below, we will not do that, in order that we can see the PowerShell object and work with its properties. Remember, this command is quite powerful due to its built-in ability to deserialize returned content into something more useable by individuals working with PowerShell.
Without viewing the nested properties we are presented the resources
and rate
properties. This should seem familiar from the above JSON. We can return the rate
property or the resources.core
property and get to limit
, remaining
, and the used
property, as well.
$NotJson = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Headers $NotJson resources rate --------- ---- @{core=; graphql=; integration_manifest=; search=} @{limit=60; remaining=60; reset=1646692433; used=0; resource=core} $NotJson.rate limit : 60 remaining : 60 reset : 1646980018 used : 0 resource : core
There is another property that we have yet to discuss, and that is the reset
property. This value is set on the first API call. It is set 60 minutes into the future, so we know when our remaining
value will return to 60, and our used
value will return to 0.
This property is recorded and stored as Epoch time (or Unix time, or Unix epoch). It is the number of seconds since January 1, 1970. The below PowerShell will covert the Epoch time to a human-readable form. We will use this further below inside a function we will use to see all of this working together.
((Get-Date 01.01.1970)+([System.TimeSpan]::fromseconds(1646980018))).ToLocalTime()
Thursday, March 10, 2022 11:26:58 PM
Like I often do, I created a function, which I will include below. I will not do a line-by-line explanation. I will discuss it, however. It accepts three parameters. One is the GitHub rate limit URI, one is the Headers we send in with each API call, and the final parameter is a URI of one of my projects on GitHub. Following the parameters, when the function is invoked, it will execute a rate limit GitHub API call and return some data, execute a GitHub API call against my project (with Out-Null
[so no output]), and then execute a rate limit GitHub API call and return some data again. This allows us to see the before rate limit information and the after rate limit information. Take a look at the example beneath the function, after you have looked it over.
function Watch-GitHubRateLimit { [CmdletBinding()] Param ( [Parameter()] $GHRateLimitUri = 'https://api.github.com/rate_limit', [Parameter()] $Headers = @{Headers = 'application/vnd.github.v3+json'}, [Parameter()] $GHGeneralUri = 'https://api.github.com/repos/tommymaynard/TMOutput' ) $Json = Invoke-RestMethod -Uri $GHRateLimitUri -Headers $Headers [PSCustomObject]@{ Invocation = 'Before' Limit = $Json.Rate.Limit Remaining = $Json.Rate.Remaining ResetEpoch = $Json.Rate.Reset ResetReadable = ((Get-Date -Date 01-01-1970)+ ([System.TimeSpan]::fromseconds($Json.rate.reset ))).ToLocalTime() Used = $Json.Rate.Used } Invoke-RestMethod -Uri $GHGeneralUri -Headers $Headers | Out-Null $Json = Invoke-RestMethod -Uri $GHRateLimitUri -Headers $Headers [PSCustomObject]@{ Invocation = 'After' Limit = $Json.Rate.Limit Remaining = $Json.Rate.Remaining ResetEpoch = $Json.Rate.Reset ResetReadable = ((Get-Date -Date 01-01-1970)+ ([System.TimeSpan]::fromseconds($Json.Rate.Reset ))).ToLocalTime() Used = $Json.Rate.Used } } Watch-GitHubRateLimit
Invocation : Before Limit : 60 Remaining : 60 ResetEpoch : 1646980115 ResetReadable : 3/10/2022 11:28:35 PM Used : 0 Invocation : After Limit : 60 Remaining : 59 ResetEpoch : 1646980115 ResetReadable : 3/10/2022 11:28:35 PM Used : 1
Now stop and go back and look at the above function. What is wrong with that? Forget GitHub and API calls (kind of). How could that function be improved?
Figure it out? Do you see all that duplicated code? Yuck. Looking at something like this should trigger a feeling that something is wrong. What we need, instead of all that duplicated code, is a nested function. I have a rewrite below. Take a look and then view the next two examples.
function Watch-GitHubRateLimit { [CmdletBinding()] Param ( [Parameter()] $GHRateLimitUri = 'https://api.github.com/rate_limit', [Parameter()] $Headers = @{Headers = 'application/vnd.github.v3+json'}, [Parameter()] $GHGeneralUri = 'https://api.github.com/repos/tommymaynard/TMOutput' ) function Get-GitHubRateLimitStats { param ($Invocation) $Json = Invoke-RestMethod -Uri $GHRateLimitUri -Headers $Headers [PSCustomObject]@{ Invocation = $Invocation Limit = $Json.Rate.Limit Remaining = $Json.Rate.Remaining ResetEpoch = $Json.Rate.Reset ResetReadable = ((Get-Date -Date 01-01-1970)+ ([System.TimeSpan]::fromseconds($Json.Rate.Reset ))).ToLocalTime() Used = $Json.Rate.Used } } Get-GitHubRateLimitStats -Invocation 'Before' Invoke-RestMethod -Uri $GHGeneralUri -Headers $Headers | Out-Null Get-GitHubRateLimitStats -Invocation 'After' } Watch-GitHubRateLimit
Invocation : Before Limit : 60 Remaining : 59 ResetEpoch : 1646980116 ResetReadable : 3/10/2022 11:28:36 PM Used : 1 Invocation : After Limit : 60 Remaining : 58 ResetEpoch : 1646980116 ResetReadable : 3/10/2022 11:28:36 PM Used : 2
Much better, but then I read this: “Note: The rate
object is deprecated. If you’re writing new API client code or updating existing code, you should use the core
object instead of the rate
object. The core
object contains the same information that is present in the rate
object.” This is taken from this Git Hub Rate limit page. Now that we know this, let’s update the final function so it uses the correct object.
function Watch-GitHubRateLimit { [CmdletBinding()] Param ( [Parameter()] $GHRateLimitUri = 'https://api.github.com/rate_limit', [Parameter()] $Headers = @{Headers = 'application/vnd.github.v3+json'}, [Parameter()] $GHGeneralUri = 'https://api.github.com/repos/tommymaynard/TMOutput' ) function Get-GitHubRateLimitStats { param ($Invocation) $Json = Invoke-RestMethod -Uri $GHRateLimitUri -Headers $Headers [PSCustomObject]@{ Invocation = $Invocation Limit = $Json.Resources.Core.Limit Remaining = $Json.Resources.Core.Remaining ResetEpoch = $Json.Resources.Core.Reset ResetReadable = ((Get-Date -Date 01-01-1970)+ ([System.TimeSpan]::fromseconds($Json.Resources.Core.Reset ))).ToLocalTime() Used = $Json.Resources.Core.Used } } Get-GitHubRateLimitStats -Invocation 'Before' Invoke-RestMethod -Uri $GHGeneralUri -Headers $Headers | Out-Null Get-GitHubRateLimitStats -Invocation 'After' } Watch-GitHubRateLimit
Invocation : Before Limit : 60 Remaining : 58 ResetEpoch : 1646980116 ResetReadable : 3/10/2022 11:28:36 PM Used : 2 Invocation : After Limit : 60 Remaining : 57 ResetEpoch : 1646980116 ResetReadable : 3/10/2022 11:28:36 PM Used : 3
I know how I feel about a post as I am writing it and those feelings stay consistent until the end. I like what I have written here. There is a great opportunity to learn about working with APIs and even some function writing. I hope is it helpful for others, obviously (as it is kind of what I do). The takeaway though is that if there is an API for something, work with it. It is fascinating to gather information from websites and web services via PowerShell. One day you will be asked to do it, so you might as well get some experience now.