Fathom’s Internal API

I’m a big fan of Fathom Analytics. It’s the perfect alternative to Google Analytics and something I’ve been using since early 2018. One thing Fathom lacks is an official API which lead me to do some pretty crazy monkey patching in order to extract data from my self-hosted Fathom instance. Rather than wait for an official API, I sent some time learning Fathom’s internal API and was surprised at how easy it is to work with.

No public API, no worries!

Fathom’s backend is written in Go. The front-end is written in Preact. The Go backend is already structured as an API for use with the front-end. With a self-hosted instance of Fathom up and running, I started poking around using Google Chrome Tools. Turns out mimicking how Preact talks to the Go backend is actually very simple. To begin, let’s look at the sign in process.

Signing into Fathom Analytics

Nothing fancy here. Just a standard put request to /api/session with the payload including the email and password in JSON format. I mimicked this request successfully using Insomnia.

Bad login attempt
Successful login attempt

Once authenticated, you can proceed and do anything the official Fathom dashboard can do. A get request to api/sites will return a full list of sites and tracking codes.

From Insomnia to real code.

Insomnia is great for a proof of concept. The next step is to write real code. While this could be written in any language I’m going to show how to write a Fathom client from WordPress using PHP. I’ll skip the part where I spent a fair amount of time within WP-CLI’s shell in order to pull these working examples together.

$fathom_instance = "https://stats.my-fathom-tracker.com";
$login_details = array( 
    'email'    => 'austin@anchor.host', 
    'password' => 'password'
);

// Authenticate to Fathom instance
$auth = wp_remote_post( "$fathom_instance/api/session", array( 
    'method'  => 'POST',
    'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
    'body'    => json_encode( $login_details )
) );

If the login was successful, then we should now have cookies within $auth['cookies']. This can be used to make authenticated get and put requests to the various internal Fathom Analytics API.

// Fetch Sites
$arguments = array(
      'cookies' => $auth['cookies']
);
$response  = wp_remote_get( "$fathom_instance/api/sites", $arguments );
$sites     = json_decode( $response['body'] )->Data;

Making a put request to api/sites will create a new site and return the site details.

// Add a new site
$arguments = array(
     'cookies' => $auth['cookies'],
     'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
     'body'    => json_encode( array ( 'name' => 'new-site-name.com' ) )
);
$response  = wp_remote_post( "$fathom_instance/api/sites", $arguments );
$new_site  = json_decode( $response['body'] )->Data;

A get request to api/stats/{site_id}/stats/site is where we can pull down the raw hourly stats. These can then be processed with PHP with a foreach loop. This example will pull down 12 months of stats and output the total pageviews.

// Fetch 12 months of stats (From June 1st 2018 to May 31st 2019)
$site_id   = 1;
$before    = strtotime( '2019-05-31 04:00:00' );
$after     = strtotime( '2018-06-01 04:00:00' );
$arguments = array(
      'cookies' => $auth['cookies']
);
$response  = wp_remote_get( "$fathom_instance/api/sites/$site_id/stats/site?before=$before&after=$after", $arguments );
$stats     = json_decode( $response['body'] )->Data;

$pageviews = 0;
$months    = array();
foreach( $stats as $key => $value ) { 
    if ( isset ( $value->Pageviews ) ) {
        $pageviews += $value->Pageviews;
    }
}
echo "Total pageviews for 12 month timespan: $pageviews";

This is interesting, but what does it all mean?

Fathom Analytics is an analytics engine. Even without an official API you can easily swap in your own custom stats interface or do deep integration with virtually anything. I plan explore this further for CaptainCore, my WordPress management toolkit. I currently manually deploy Fathom tracking codes however that could easily be automated. 💯