Using page template as custom API endpoint

The WordPress REST API is awesome and fully extendable to do your own custom crazy ideas. The following I’m about to explain goes against all of that. Instead of extending the WordPress REST API I’m creating my own custom API endpoint using nothing other then a custom page template.

Learn APIs by building the most basic version from scratch

Digging in deep with the WordPress REST API can be overwhelming and requires a good understanding of authentications, callbacks, hooks and filters. The only requirement here is a basic understanding of how to create a custom page template.

Building an API endpoint from strach is not recommended for production use. It is a great way to learn how APIs works. Here is the shell of the new page template we’ll be working with.

<?php
/**
 * Template Name: Custom API Endpoint
 */

// Capture query string as variables
$token = $_GET['token'];

// Define a random token to valid request. No symbols. 
$token_key = "RANDOM_TOKEN_KEY_HERE";

// Verifies valid token
if ( $token == $token_key ) {

    // Run custom code here

} else {  // No valid token found so return some error page
get_header(); ?>

    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">

            <section class="error-404 not-found">
                <header class="page-header">
                    <h1 class="page-title"><?php _e( 'Oops! That page can’t be found.', 'twentysixteen' ); ?></h1>
                </header><!-- .page-header -->

                <div class="page-content">
                    <p><?php _e( 'It looks like nothing was found at this location. Maybe try a search?', 'twentysixteen' ); ?></p>

                    <?php get_search_form(); ?>
                </div><!-- .page-content -->
            </section><!-- .error-404 -->

        </main><!-- .site-main -->

        <?php get_sidebar( 'content-bottom' ); ?>

    </div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

} 
?>

Use a custom token in query string and https for security

With https everything in the url is encrypted. This can lock down the custom API like this https://example.com/custom-api/?token=RANDOM_TOKEN_KEY_HERE. Our custom code would only run if the token in the URL matches our preassigned token in the custom page template.

While this isn’t full proof, with some extra features like expiring tokens, it is similar to how most popular services like Amazon S3 protect their private files. Expiring tokens is not something I’m going to get into here.

Updating ACF Fields with custom API

With a little addition we can start to do some pretty powerful things. With the following we can update custom ACF fields just by defining a few field key and adding some querystring for the updated values.

<?php
/**
 * Template Name: Custom API Endpoint

    # Updating views and storage 
    https://example.com/custom-api/?views=9435345&storage=2334242&site_id=2394&token=token_key

*/

// Capture query string as variables
$token   = $_GET['token'];
$storage = $_GET['storage'];
$views   = $_GET['views'];
$site_id = $_GET['site_id'];

// Define a random token to valid request. No symbols. 
$token_key = "RANDOM_TOKEN_KEY_HERE";

// Verifies valid token
if ( $token == $token_key ) {

    // Run custom code here
    if ($views and $storage) {
        update_field("field_57e0b2b17eb2a", $storage, $site_id);
        update_field("field_57e0b2c07eb2b", $views, $site_id);
    }

}

Adding new custom posts with custom API

Similarly it can be extended to generate new custom post based on values in the querystrings.

<?php
/**
 * Template Name: Custom API Endpoint

    # Adding new custom post type
    https://example.com/custom-api/?archive=anchorhost1.wpengine.com-2016-10-22.tar.gz&storage=235256&token=token_key

*/

// Capture query string as variables
$token   = $_GET['token'];
$storage = $_GET['storage'];
$archive = $_GET['archive'];

// Define a random token to valid request. No symbols. 
$token_key = "WacjDD05u7rJRIPKn7lC0n73kiUVcRIt";

// Verifies valid token
if ( $token == $token_key ) {

    // Run custom code here
    if ($archive and $storage) {

        // Create post object
        $my_post = array(
          'post_title'    => "Snapshot",
          'post_type'     => 'snapshot',
          'post_status'   => 'publish'
        );

        // Insert the post into the database
        $snapshot_id = wp_insert_post( $my_post );

        update_field("field_580b7cf4f2790", $archive, $snapshot_id);
        update_field("field_580b9776f2791", $storage, $snapshot_id);
        update_field("field_580b9784f2792", $site_id, $snapshot_id);

        // Adds snapshot ID to title
        $my_post = array(
          'ID'            => $snapshot_id,
          'post_title'    => "Snapshot ". $snapshot_id,
        );

        wp_update_post($my_post);
    }

}

Putting it all together

The following shows off everything in a single page template with an additional feature, assigning an ACF relationship field based on a custom field record lookup.

<?php
/**
 * Template Name: Custom API Endpoint

    This is a collection of custom functions. Currently it handles the following:

    * Adding new custom post type
    * Updating custom ACF Fields
    * Assigning relationship ACF Field

    Examples:

    # Updating views and storage 
    https://example.com/custom-api/?views=9435345&storage=2334242&site_id=2394&token=token_key

    # Adding new custom post type
    https://example.com/custom-api/?archive=anchorhost1.wpengine.com-2016-10-22.tar.gz&storage=235256&token=token_key

    # Assigning relationship ACF Field
    https://example.com/custom-api/?server=104.197.69.102&token=token_key

*/

// Capture query string as variables
$token   = $_GET['token'];
$storage = $_GET['storage'];
$archive = $_GET['archive'];
$views   = $_GET['views'];
$server  = $_GET['server'];

// Define a random token to valid request. No symbols. 
$token_key = "RANDOM_TOKEN_KEY_HERE";

// Verifies valid token
if ( $token == $token_key ) {

    // Run custom code here
    if ($views and $storage) {
        update_field("field_57e0b2b17eb2a", $storage, $site_id);
        update_field("field_57e0b2c07eb2b", $views, $site_id);
    }
    // Generate a new snapshot. 
    if ($archive and $storage) {

        // Create post object
        $my_post = array(
          'post_title'    => "Snapshot",
          'post_type'     => 'snapshot',
          'post_status'   => 'publish'
        );

        // Insert the post into the database
        $snapshot_id = wp_insert_post( $my_post );

        update_field("field_580b7cf4f2790", $archive, $snapshot_id);
        update_field("field_580b9776f2791", $storage, $snapshot_id);
        update_field("field_580b9784f2792", $site_id, $snapshot_id);

        // Adds snapshot ID to title
        $my_post = array(
          'ID'            => $snapshot_id,
          'post_title'    => "Snapshot ". $snapshot_id,
        );

        wp_update_post($my_post);
    }
    // Assigning relationship ACF Field
    if ($server) {
        // args
        $args = array(
            'numberposts'   => 1,
            'post_type'     => 'server',
            'meta_key'      => 'address',
            'meta_value'    => $server
        );

        // query
        $the_query = new WP_Query( $args );

        if( $the_query->have_posts() ): 

            while( $the_query->have_posts() ) : $the_query->the_post(); 

                $server_id = get_the_ID();

                update_field("field_5803aaa489114", $server_id, $site_id);

            endwhile; 

        endif; 

    }

} else {  // No valid token found so return some error page
get_header(); ?>

    <div id="primary" class="content-area">
        <main id="main" class="site-main" role="main">

            <section class="error-404 not-found">
                <header class="page-header">
                    <h1 class="page-title"><?php _e( 'Oops! That page can’t be found.', 'twentysixteen' ); ?></h1>
                </header><!-- .page-header -->

                <div class="page-content">
                    <p><?php _e( 'It looks like nothing was found at this location. Maybe try a search?', 'twentysixteen' ); ?></p>

                    <?php get_search_form(); ?>
                </div><!-- .page-content -->
            </section><!-- .error-404 -->

        </main><!-- .site-main -->

        <?php get_sidebar( 'content-bottom' ); ?>

    </div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

} 
?>