Kinsta’s GraphQL Endpoint

Over the past few weeks, I’ve been resisting changes to how SSLs work with Kinsta & Cloudflare. To rant slightly, here is a brief timeline of what’s been happening, including my responses.

  • ๐Ÿ‘ Kinsta integrates with Cloudflare switching from a regular Let’s Encrypt SSL to a Cloudflare Wildcard SSL.
  • ๐Ÿ‘Ž Cloudflare Announces SSL changes to Wildcard SSLs, due to industry changes.
  • ๐Ÿ‘ Kinsta responds and starts sending out emails with domains and new TXT verification records for SSL renewals.
  • ๐Ÿ‘ I write a robot to automatically read the emails and perform DNS updates.
  • ๐Ÿ‘Ž Kinsta removes the TXT verification record from the email itself, due to security issues over email.
  • ๐Ÿ‘ I find another way to extract the TXT verification records from Kinsta and resume my email robot. I’ll give you a hint…the Kinsta GraphQL endpoint.

This is all slightly annoying as 99% of Anchor Hosting customers do not need a wildcard SSL. I’d switch to a non-wildcard alternative however at this time Kinsta doesn’t have that as an option. What’s more unfortunate is that before Kinsta integrated with Cloudflare, all of my customers were using non-wildcard SSLs. ๐Ÿ˜ซ

Kinsta is a company driven by innovation. ๐Ÿงช

One of the reasons I was drawn to Kinsta was because they keep innovating their platform. In the web hosting space that’s actually rare. While I’m a bit annoyed at the current SSL situation I’m overall in favor of Kinsta to keep moving forward even if that creates some pain. Just don’t tell me it’s not possible to automate something. I will find a way ๐Ÿ™‚.

Ranting over Kinsta support

Poking around MyKinsta with Chrome DevTools

I’m familiar with two ways to automate something when no API exists. One is with headless Chrome which will utilize your username and password from a programmed script. Another is by interacting directly with the internal API with authorization headers and/or cookies copied from your browser session. Both are messy and can break at any time.

Working with a private API requires you to first sign in using a real browser. Next, open up the Network tab within Chrome DevTools and start hunting around. If you’re lucky, you’ll find that unique token or authentication header that unlocks the entire application to your will. Most likely you’ll be stuck trying to piece together how the internal calls chain together and talk to the authentication system.

I started working on a script to sign in Kinsta with headless Chrome however due to performance issues I abandoned that idea. After an afternoon of playing around with Kinsta’s internal GraphQL endpoint, I had a breakthrough. Real data and everything is extremely fast. Their endpoint is crazy powerful and actually a pleasure to work with, once you understand the steps to reverse engineer a web request.

Reverse engineering a simple request to MyKinsta

The first thing I noticed is that MyKinsta primarily talks to https://my.kinsta.com/graphql. Before attempting to write PHP code, I used Insomnia, an open-source API client, to recreate requests I was seeing in Chrome DevTools. I discovered that authentication is handled by the request header called X-Token. Your current token can be copied from the Chrome DevTools network tab, on any activity going to their GraphQL endpoint.

The main thing I’m interested in is retrieving TXT verification records. So within MyKinsta I manually viewed a few TXT verification records with the network tab open and found the query for FullSiteDomains and copied that into Insomnia. After a few 500 internal server errors, Success!

Interacting with MyKinsta’s GraphQL endpoint with PHP

For my situation, I wanted to ask Kinsta if a domain had TXT verification records. In PHP, something like this.

// Attempt to retrieve from Kinsta
if ( empty( $txt ) ) {
        $txt = self::attempt_fetch_verification_record( $domain );
}

This requires a bit of understanding of how and where Kinsta stores the data. We can ask Kinsta for TXT records using the FullSiteDomains query however we first need to know Kinsta’s corresponding $idSite and $idEnvironment. For the sake of keeping this example simple, let’s just assume we know how to do that translation. We can then talk directly to MyKinsta’s GraphQL endpoint using wp_remote_post as shown below.

public function attempt_fetch_verification_record ( $domain ) {

        // Do some magic here to translate a $domain into a corresponding Kinsta $idSite and $idEnvironment.

        $idSite        = "my-site-id";
        $idEnvironment = "my-environment-id";

        $data = [ 
            'timeout' => 45,
            'headers' => [
                'Content-Type' => 'application/json',
                'X-Token'      => MY_KINSTA_TOKEN
            ],
            'body'    => json_encode( [
                "variables" => [ 
                    "idSite"        => $idSite,
                    "idEnvironment" => $idEnvironment
                ],
                "operationName" => "FullSiteDomains",
                "query"         => 'query FullSiteDomains($idSite: String!, $idEnvironment: String!) {
                    site(id: $idSite) {
                      id
                      environment(id: $idEnvironment) {
                        id
                        customHostnames {
                          id
                          rootDomain
                          idRootDomain
                          verificationRecords {
                            name
                            value
                            __typename
                          }
                        }
                      }
                    }
                  }'
            ] )
        ];

        $response = wp_remote_post( "https://my.kinsta.com/graphql", $data );

        if ( is_wp_error( $response ) ) {
            $to      = get_option('admin_email');
            $subject = "Communication with Kinsta error";
            $headers = [ 
                'Content-Type: text/html; charset=UTF-8',
            ];
            $body    = $response->get_error_message();
            wp_mail( $to, $subject, $body, $headers );
            return "";
        }

        $response = json_decode( $response['body'] );
        foreach( $response->data->site->environment->customHostnames as $record ) {
            if ( $record->verificationRecords ) {
                foreach ($record->verificationRecords as $item ) {
                    if ( $item->name == $domain ) {
                        return $item->value;
                    }
                }
            }
        }
        return "";

}

Finding the useful data under $response->data->site->environment->customHostnames took a bit of discovery. For that, I used wp shell and manually dug through the results. With WordPress’ interactive console you can get the full response once and then manually go through the levels one layer at a time.

// Full response
$response

// One level deep
$response->data

// Next few levels
$response->data->site
$response->data->site->environment
$response->data->site->environment->customHostnames

// First domain TXT verification record
$response->data->site->environment->customHostname[0]->verificationRecords[0]->value

My final code is viewable here.

MyKinsta’s GraphQL endpoint has the potential to unlock next-level automation at Kinsta.

In order for MyKinsta’s GraphQL endpoint to be useful, it needs to be officially acknowledged and supported by a proper authentication system. There needs to be a way to generate a persistent API token. Reusing an internal token from real browser login is not reliable and can break at any moment.

I personally see no reason for Kinsta to make a separate MyKinsta API when their existing GraphQL endpoint is a joy to work with and feature complete. I mean it’s preciously what’s powering their own dashboard. Please, Kinsta, just make MyKinsta’s GraphQL endpoint the official API. ๐Ÿ™