Tag Archives: ConvertTo-Json

GitHub Rate Limit REST API, JSON, and Epoch Time

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.