Introduction to The Events Calendar REST API

What is a REST API, again?

If you work at all with the web, you will have probably heard of many "REST APIs", particularly the WordPress REST API. Any article dealing with a REST API devotes its beginning to explaining what a REST API is. While we follow that pattern, we'll try to be as concise as possible.

The "API" acronym stands for "Application Programming Interface". In layman's terms, it means the tools and controls (the Interface) the server (typically a website) makes available to the client (the Application in API - typically a browser, app, or other website) for accessing data. Our main concern with APIs typically involves the format the data is exchanged in - "how do we use this?"

The "REST" acronym stands for "REpresentational State Transfer" which means that the state of the transaction between a client and the server providing a RESTful API is wholly contained in the response the client receives, so "WYSIWYG" for data. HTTP protocol-based communications (what a browser uses to "get stuff" from the web) are considered to be the only domain where it makes sense to talk about REST APIs. We use the term "consume" to describe when a client uses an API (the Transfer).

"State" is the representation of how a client got what it currently holds: if you have $5 in your pocket and a shop receipt saying you paid for a $5 meal with a $10 bill, you know what you have ($5) and how you got there (spent $5 and got $5 in change). If all you had was the $5 bill, the scenarios leading to that might include you winning the lottery, losing it all to tricky raccoons, and finding a $5 bill on the ground.

So, for an API to be "RESTful", said API should not require clients consuming it to keep track of a "state" and make full use of HTTP methods.

HTTP is a protocol, the one you use to commonly access a site using a URL like http://how-not-to-get-scammed-by-raccoons.org. It provides ways to do what you need to do on the web. Commonly you use the GET method to retrieve data from websites. In the case above, the HTTP request would look like this:

GET http://how-not-to-get-scammed-by-raccoons.org/index.php

But GET is not the only existing HTTP method. When you submit a form, you are usually making a POST request to a URL. That request represents your intent to send data to the site instead of receiving data from it. When completing a login form, you press the submit button in the form, and the browser would make a request like this:

POST http://how-not-to-get-scammed-by-raccoons.org/login.php
    user=luca&password=secret

Since you are sending something, the user=luca&password=secret string represents what you are sending to the server. The syntax above does not correspond to any particular API and is merely for the purposes of this article--do not use that in your code, or raccoons will invade your home!

GET and POST are the most widely used HTTP methods, but there are more: DELETE is used to remove stuff from the server, PATCH or PUT are commonly used to edit data on the server, and HEAD and OPTIONS are used to fetch information about the server.

When an API makes full and correct use of the methods above, it is then considered "RESTful". If I wanted to update my password on the site, I could use:

PUT http://how-not-to-get-scammed-by-raccoons.org/api/users/luca
    password=new-secret

Notice I'm now using a different URL to perform the required operation, one that ends in /api/users/luca. This could be translated as "call the api on http://how-not-to-get-scammed-by-raccoons.org: we need to update the details for one of the users, luca is the name, and set the password to new-secret".

In technical terms, /api is the site's REST API root path, /api/users is an endpoint provided by the API, and luca is a parameter of the specific call.

If we wanted to remove my user from the site, we would issue this request to the server:

DELETE http://how-not-to-get-scammed-by-raccoons.org/api/users/luca

Now you know why a typical WordPress "Trash" link, which employs URLs such as http://tribe.localhost/wp-admin/post.php?post=6&action=trash&_wpnonce=25a5cdc0ba, is not RESTful: you are deleting something using the HTTP GET method.

Reading the examples, it's clear that the site might authorize anyone to make GET requests, but it would probably impose limits (in the form of authentication requirements) on performing operations like adding, updating and deleting a user. Or any kind of data, really. Almost any REST API, the WordPress and The Events Calendar APIs being no exception, will make full use of authorization to allow or not allow a user to perform an action. There are many ways to authenticate a user--for the time being, just keep the idea in mind.

The last piece of information you need to fully qualify as a REST API expert, is the notion of HTTP response codes. These are numbers the server will use to tell the client what happened in response to a certain request. You are probably familiar with some: 404 means what you were looking for was not found, 403 means you are not authorized to do what you tried to do, 500 means an internal error happened on the server, and so on. Feast your eyes on the full list of status codes here, and get ready to explore The Events Calendar REST API.

A REST API for The Events Calendar: how can we use it?

We built The Events Calendar REST API to allow our users and ourselves to consume and manage events created with The Events Calendar from anywhere. In its most basic form, a REST API separates the content from its presentation, freeing developers and users from the unbreakable paradigm of a WordPress site and a WordPress theme.

"Anywhere" is a big concept, so here are some examples:

  • We are updating the administration UI used in The Events Calendar to manage events using the REST API in place of the standard WordPress flow. This means more responsive UI, no more page reloads, and faster iterations.
  • Themes will be able to access events, venues, and organizers' data without the need to use the plugin PHP functions, and developers will be able to present that data to users in any way they want. Creating new views, widgets, and calendars will become easier than ever.
  • Sites not using PHP or WordPress will be able to use a WordPress installation as a content management system to manage events, venues, and organizers while the site's front-end is maintained in a preferred language.
  • Android, iOS, and other mobile application developers will be able to use a WordPress backend to manage events and present the same information to different users on different platforms, with "thin" clients, in a way that fits their target audience and platform.

Since The Events Calendar REST API is built on top of the WordPress REST API it inherits its excellent code, robustness, and security.

A first example

Now that we've sung the praises of The Events Calendar REST API, it's time to see it in action!

The Events Calendar REST API is free and bundled with The Events Calendar plugin. It is PHP 5.2 compatible and requires WordPress 4.5 or above. Once you have installed the latest version of The Events Calendar, simply activate, and you're ready to get started.

In the following examples, we'll assume the site on which The Events Calendar REST API is active can be reached at http://tribe.localhost--so keep that in mind.

Once The Events Calendar is active, we'll create an event, set its details, publish it, and preview it in the browser:

This is the familiar look of an event in the context of the Twenty Seventeen theme. Let's now head, using a browser, to the address http://tribe.localhost/wp-json/tribe/events/v1/events:

Uhm… not that nice. But wait: that's the essence of what The Events Calendar REST API does: providing the raw data about the event without all the presentation stuff surrounding it.

Here is the response in a pretty format:


{
    "events": [{
        "id": 6,
        "global_id": "tribe.localhost?id=6",
        "global_id_lineage": [
            "tribe.localhost?id=6"
        ],
        "author": "1",
        "status": "publish",
        "date": "2017-09-21 11:57:52",
        "date_utc": "2017-09-21 11:57:52",
        "modified": "2017-09-21 12:03:35",
        "modified_utc": "2017-09-21 12:03:35",
        "url": "http://tribe.localhost/event/example-event-1/",
        "rest_url": "http://tribe.localhost/wp-json/tribe/events/v1/events/6",
        "title": "REST Event 1",
        "description": "Example content",
        "excerpt": "",
        "image": {
            "url": "http://tribe.localhost/wp-content/uploads/2017/09/raccoon.jpg",
            "id": "8",
            "extension": "jpg",
            "width": 220,
            "height": 147,
            "sizes": {
                "thumbnail": {
                    "width": 150,
                    "height": 147,
                    "mime-type": "image/jpeg",
                    "url": "http://tribe.localhost/wp-content/uploads/2017/09/raccoon-150x147.jpg"
                }
            }
        },
        "all_day": false,
        "start_date": "2017-09-21 08:00:00",
        "start_date_details": {
            "year": "2017",
            "month": "09",
            "day": "21",
            "hour": "08",
            "minutes": "00",
            "seconds": "00"
        },
        "end_date": "2017-09-21 17:00:00",
        "end_date_details": {
            "year": "2017",
            "month": "09",
            "day": "21",
            "hour": "17",
            "minutes": "00",
            "seconds": "00"
        },
        "utc_start_date": "2017-09-21 08:00:00",
        "utc_start_date_details": {
            "year": "2017",
            "month": "09",
            "day": "21",
            "hour": "08",
            "minutes": "00",
            "seconds": "00"
        },
        "utc_end_date": "2017-09-21 17:00:00",
        "utc_end_date_details": {
            "year": "2017",
            "month": "09",
            "day": "21",
            "hour": "17",
            "minutes": "00",
            "seconds": "00"
        },
        "timezone": "UTC+0",
        "timezone_abbr": "",
        "cost": "$5",
        "cost_details": {
            "currency_symbol": "$",
            "currency_position": "prefix",
            "values": ["5"]
        },
        "website": "http://raccoons-ate-my-neighbour.com",
        "show_map": false,
        "show_map_link": false,
        "hide_from_listings": false,
        "sticky": false,
        "featured": false,
        "categories": [{
            "name": "rest-stuff",
            "slug": "rest-stuff",
            "term_group": 0,
            "term_taxonomy_id": 2,
            "taxonomy": "tribe_events_cat",
            "description": "",
            "parent": 0,
            "count": 1,
            "filter": "raw",
            "id": 2,
            "urls": {
                "self": "http://tribe.localhost/wp-json/tribe/events/v1/categories/2",
                "collection": "http://tribe.localhost/wp-json/tribe/events/v1/categories"
            }
        }],
        "tags": [{
            "name": "example",
            "slug": "example",
            "term_group": 0,
            "term_taxonomy_id": 3,
            "taxonomy": "post_tag",
            "description": "",
            "parent": 0,
            "count": 1,
            "filter": "raw",
            "id": 3,
            "urls": {
                "self": "http://tribe.localhost/wp-json/tribe/events/v1/tags/3",
                "collection": "http://tribe.localhost/wp-json/tribe/events/v1/tags"
            }
        }, {
            "name": "raccoon",
            "slug": "raccoon",
            "term_group": 0,
            "term_taxonomy_id": 4,
            "taxonomy": "post_tag",
            "description": "",
            "parent": 0,
            "count": 1,
            "filter": "raw",
            "id": 4,
            "urls": {
                "self": "http://tribe.localhost/wp-json/tribe/events/v1/tags/4",
                "collection": "http://tribe.localhost/wp-json/tribe/events/v1/tags"
            }
        }],
        "venue": {
            "id": 10,
            "author": "1",
            "status": "publish",
            "date": "2017-09-21 12:00:59",
            "date_utc": "2017-09-21 12:00:59",
            "modified": "2017-09-21 12:00:59",
            "modified_utc": "2017-09-21 12:00:59",
            "url": "http://tribe.localhost/venue/raccoon-org/",
            "venue": "raccoon Org",
            "description": "Venue Description",
            "address": "123, raccoon Ave.",
            "city": "Raccon City",
            "country": "United States",
            "province": "raccoon County",
            "state": "NH",
            "zip": "12345",
            "phone": "+112341234",
            "website": "http://raccoons.org",
            "stateprovince": "NH",
            "show_map": true,
            "show_map_link": true,
            "global_id": "tribe.localhost?id=10",
            "global_id_lineage": ["tribe.localhost?id=10"]
        },
        "organizer": [{
            "id": 11,
            "author": "1",
            "status": "publish",
            "date": "2017-09-21 12:01:48",
            "date_utc": "2017-09-21 12:01:48",
            "modified": "2017-09-21 12:01:48",
            "modified_utc": "2017-09-21 12:01:48",
            "url": "http://tribe.localhost/organizer/a-first-raccoon/",
            "organizer": "A first raccoon",
            "description": "Shy but packing a lot of punch.",
            "phone": "+1233434234",
            "website": "http://first-raccoon.com",
            "email": "first@raccoon.com",
            "global_id": "tribe.localhost?id=11",
            "global_id_lineage": ["tribe.localhost?id=11"]
        }, {
            "id": 12,
            "author": "1",
            "status": "publish",
            "date": "2017-09-21 12:02:43",
            "date_utc": "2017-09-21 12:02:43",
            "modified": "2017-09-21 12:02:43",
            "modified_utc": "2017-09-21 12:02:43",
            "url": "http://tribe.localhost/organizer/a-second-raccoon/",
            "organizer": "A second raccoon",
            "description": "Gentle and deadly.",
            "phone": "+134473847",
            "website": "http://raccoon-for-me.com",
            "email": "racc00n@raccoon.for-me.com",
            "global_id": "tribe.localhost?id=12",
            "global_id_lineage": ["tribe.localhost?id=12"]
        }]
    }],
    "rest_url": "http://tribe.localhost/wp-json/tribe/events/v1/events/?page=1&per_page=10&start_date=2017-09-21 01:59:00&end_date=2019-09-21 13:06:27",
    "total": 1,
    "total_pages": 1
}

This time, I've specified some parameters for my request:

  • per_page=2 which means "show me two events per page"
  • page=2 which means "show me the second page"

The REST API is using those parameters to narrow down the query.
The word "query" should sound familiar to WordPress developers, and that's because many of the WordPress and The Events Calendar REST API parameters share a name with those used in a WP_Query object. Some of those parameters will be set to a default value by the REST API, and that explains why the rest_url in the data does not look exactly like the one we typed.

What if we only want to get a single event and not all of them?
It's sufficient to pass the event post ID as a path parameter in the URL, like this:

GET http://tribe.localhost/wp-json/tribe/events/v1/events/6

The data returned is identical to the one shown above but for that specific event only (content removed for brevity):


{
    "id": 6,
    "author": "1",
    "title": "REST Event 1",
    "description": "Example content" 
}

Modifying the response

So far, we've seen the stock response format from The Events Calendar REST API, but what if we want to change the contents of that response?
As WordPress developers managing a site dedicated to victims of scams perpetrated by raccoons, we might provide an educated guess about how much an event "looks suspicious" to us. We do that by allowing the site editors to set a _raccoon_suspect_level custom field (aka "meta field") value ranging from 1 to 5. As the good and honest developers that we are, we want that value to show up in the REST API response: we can use the tribe_rest_single_event_data filter to do exactly that.

The following code should live in a plugin or in the current theme's functions.php file:


add_filter( 'tribe_rest_single_event_data', 'raccoon_add_suspect_level' );

function raccoon_add_suspect_level( array $event_data ) {
    $event_id = $event_data['id'];

    $level = get_post_meta( $event_id, '_raccoon_suspect_level', true );

    if ( empty( $level ) ) {
        // let's be wary...
        $level = '4';
    }

    $event_data['raccoon_supect_level'] = $level;

    return $event_data;
}

And see that pop up in the response (content removed for brevity):


{
    "id": 6,
    "author": "1",
    "status": "publish",
    "title": "REST Event 1",
    "raccoon_supect_level": "5",
    description: "Example content ",
    "json_ld": {
        "@context ": "http: //schema.org",
        "@type": "Event",
        "name": "REST Event 1",
        "raccoon_supect_level": "1",
        "description": "Example content ",
        "image ": "http: //tribe.localhost/wp-content/uploads/2017/09/raccoon.jpg",
        "url": "http://tribe.localhost/event/example-event-1/",
        "startDate": "2017-09-21 08:00:00",
        "endDate": "2017-09-21 17:00:00",
        "location": {
            "@type": "Place",
            "name": "raccoon Org",
            "description": "Venue Description",
            "url": false,
            "address": {
                "@type": "PostalAddress",
                "streetAddress": "123, raccoon Ave.",
                "addressLocality": "Raccoon City",
                "addressRegion": "NH",
                "postalCode": "12345",
                "addressCountry": "United States"
            },
            "telephone": "+112341234",
            "sameAs": "http://raccoons.org"
        },
        "organizer": {
            "@type": "Person",
            "name": "A first raccoon",
            "description": "Shy but packing a lot of punch.",
            "url": false,
            "telephone": "+1233434234",
            "email": "one@first-raccoon.com",
            "sameAs": "http://first-raccoon.com"
        },
        "offers": {
            "@type": "Offer",
            "price": "5",
            "url": "http://tribe.localhost/event/example-event-1/"
        }
    },
    "raccoon_supect_level": "5"
}

But wait for a second: what's that json_ld stuff in the response?
We haven't shown it before, but, when grabbing a single event, the REST API will also provide the JSON LD data for the event. Again. Information. A lot of it.
But what if you don't need that piece of information and would like to remove it from the response?
The same principle applies--filter the response data and remove it.

add_filter( 'tribe_rest_single_event_data', 'raccoon_remove_json_ld_data' );

function raccoon_remove_json_ld_data( array $event_data ) {
    unset( $event_data['json_ld'] );

    return $event_data;
}

The code is full of filters you can use to customize your responses, not only for events but for venues and organizers as well. So we're pretty confident there will always be a way for you to make it work the way you want it to. Take a look around in the src/Tribe/REST/V1 folder to begin with.

A living Swagger.io documentation

There is so much you can do with The Events Calendar REST API that you might get lost. Figuratively speaking, of course.

Instead of having to parse page after page of out-of-date documentation, Post-It notes, and Slack messages, The Events Calendar REST API comes with a documentation endpoint you can hit any time you're lost. You can find it at /wp-json/tribe/events/v1/doc.

Hitting the endpoint in a browser (http://tribe.localhost/wp-json/tribe/events/v1/doc in our example) will provide--you probably guessed it--a JSON format object detailing all the functionalities available on the REST API. An insanely large amount of content has been omitted for your comfort:

{
    "swagger": "2.0",
    "info": {
        "version": "1.0.0",
        "title": "The Events Calendar REST API",
        "description": "The Events Calendar REST API allows accessing upcoming events information easily and conveniently."
    },
    "host": "tribe.localhost",
    "basePath": "/wp-json/tribe/events/v1/",
    "schemes": [
        "http"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/doc": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Returns the documentation for The Events Calendar REST API in Swagger consumable format."
                    }
                }
            }
        },
        "/events": {
            "get": {
                "parameters": [
                    {
                        "in": "query",
                        "type": "integer",
                        "description": "The archive page to return",
                        "required": false,
                        "default": 1,
                        "name": "page"
                    },
                    {
                        "in": "query",
                        "type": "integer",
                        "description": "The number of events to return on each page",
                        "required": false,
                        "default": "10",
                        "name": "per_page"
                    },
                }
            }
        }
    }
}

Why a JSON object?

That entry at the beginning of the response saying "swagger": "2.0" tells you that The Events Calendar REST API documentation comes in Swagger.io format. If you copy and paste the whole response into the Swagger.io API editor, it will be converted into easily readable documentation:

This has several advantages:

  1. The documentation is the code itself. As we, or you, update the code, the documentation will be updated with it.
  2. You can modify the documentation while modifying the code: no more documentation in one place and code in the other.
  3. You can customize The Events Calendar REST API to your liking and host a dedicated documentation site.
  4. It's really cool. :)

What else? Oh, right--since we provide documentation in Swagger.io format, you can generate client applications for The Events Calendar REST API from the Swagger.io editor. With one click.

Since The Events Calendar REST API documentation is generated by WordPress code, you can filter that too. What about documenting that raccoon_suspect_level field from before?

add_filter( 'tribe_rest_swagger_event_documentation', 'raccoon_document_suspect_level' );

function raccoon_document_suspect_level( array $documentation ) {
    $documentation['properties']['raccoon_suspect_level'] = array(
        'type'        => 'integer',
        'description' => __( 'How much the event looks like a raccoon scam in a 1 to 5 range', 'raccoon' ),
    );

    return $documentation;
}

And here is the field in the Swagger.io output:

A JavaScript example of authentication

So far we've limited our request to what is publicly available on the site. By default, The Events Calendar REST API will show events in the same way the plugin would: if you are a visitor, a user who is not logged in, or a user with subscriber-level access, you will be shown public events, venues, or organizers only. If you are an editor up to an administrator, you will be shown private and draft posts, too.

Let's try and see an example of how basic, cookie-based authentication would work in the context of the crappiest theme ever.

The way WordPress knows "who you are", as a user, is by using cookies and "nonces". When we want to perform operations reserved for logged-in users with specific roles, WordPress will print on the page a string (a nonce) that has been generated using the information in the authentication cookies and a specific action. In the "Trash" link shown in the first paragraph…

http://tribe.localhost/wp-admin/post.php?post=6&action=trash&_wpnonce=25a5cdc0ba

… I'm asking WordPress to delete an event with ID 6, the action is trash, and the nonce is 25a5cdc0ba. This nonce string can only be used by me to trash that post.

The Events Calendar REST API will support (extending the WordPress REST API) cookie-based authentication out of the box, but it's ready to support any authentication method supported by WordPress REST API by extension. We did not reinvent the wheel.

With that in mind, let's write a JavaScript-based theme that will show 3 upcoming events to the user on the site's index page. We're creating a Twenty Seventeen child theme to piggy-back on the good stuff.

In the theme's functions.php file, besides loading the parent theme scripts and styles, we'll load the child theme script, /js/rest-theme.js, and the nonce we need:


// file functions.php

add_action( 'wp_enqueue_scripts', 'twentyseventeen_parent_theme_enqueue_styles' );

function twentyseventeen_parent_theme_enqueue_styles() {
    wp_enqueue_style( 'twentyseventeen-style', get_template_directory_uri() . '/style.css' );
    wp_enqueue_style( 'rest-style',
        get_stylesheet_directory_uri() . '/style.css',
        array( 'twentyseventeen-style' )
    );
    
    // enqueue the theme script...
    wp_enqueue_script( 'rest-theme-js', get_stylesheet_directory_uri() . '/js/rest-theme.js', array( 'jquery' ) );

    // ...and localize The Events Calendar REST API information and nonce
    wp_localize_script( 'rest-theme-js', 'restTheme',
        array( 'root' => esc_url_raw( tribe_events_rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ) ) );
}

We replace the theme's index.php file to scaffold an empty body that we'll fill with events fetched via The Events Calendar REST API:


// file index.php

Upcoming Events

Finally, in the /js/rest-theme.js file, we fetch the events when the page is ready:

(
   function( $ ) {
       if ( undefined === restTheme ) {
           return;
       }

       var renderEvents = function( response ) {
           var eventsNode = null;

           if ( response.events.length > 0 ) {
               var eventsNode = $( '<ul>' ); 
                   
                for ( var event of response.events ) { 
                    var eventNode = $( '<li>' ); 
                    eventNode.text( event.title ); 
                    eventNode.appendTo( eventsNode ); 
                } 
            } else { 
                var eventsNode = $( '' ); 
                eventsNode.text( 'No upcoming events found!' ); 
            } 

            eventsNode.appendTo( $( '#rest-events' ) ); 
        };
         
        var showEvents = function() {
            $.ajax( { 
                // get The Events Caleandar REST API root URL read from the localized object 
                url: restTheme.root + 'events', 
                method: 'GET', 
                // set the `X-WP-Nonce` header to the nonce value read from the localized object 
                beforeSend: function( xhr ) { 
                    xhr.setRequestHeader( 'X-WP-Nonce', restTheme.nonce ); 
                }, 
                // set some request data 
                data: { 'page': 1, 'per_page': 3, } 
            } )
            // when done render the events list 
            .done( renderEvents ); 
        }; 

        $( document ).ready( function() { showEvents(); } ); 
    } 
)( jQuery )

In this last file, we can see the cookie-based authentication in action. It consists, quite simply, of setting the X-WP-Nonce header on the AJAX request. WordPress will check the nonce against the content of the cookies for our user and set the user accordingly.

But what happens if the wrong nonce is passed? Or if the nonce is expired? Nothing dangerous: the user will be set to 0, the logged-out user, and capabilities will follow.

To test that the authentication works, we've created two public events that anyone can see and a private event that only users with the edit_posts capability will be able to see. Visiting the site as a logged-out user (like a site visitor), we see this:

While visiting the site as an administrator, a user that can edit posts by default, we see this:

This will be true for any type of authorization used to authenticate the users, not just for cookie-based authentication.

Now, what about trying some really dangerous things, like trashing events?

Let's add a button beside each event title in the /js/rest-theme.js file that will allow users to trash the corresponding event:

(
    (
    function( $ ) {
        if ( undefined === restTheme ) {
            return;
        }

        var renderEvents = function( response ) {
            var eventsNode = null;

            if ( response.events.length > 0 ) {
                var eventsNode = $( '<ul>' ); 
                var eventNodeProps = { style: 'margin-bottom: 1em;' }; 
                for ( var event of response.events ) { 
                    var eventNode = $( '<li>', eventNodeProps ); 
                    eventNode.text( event.title ); 
                    var buttonProps = { 'data-event-id': event.id, style: 'padding: 5px; margin-left: 1em;' }; 
                    var eventNodeButton = $( '<button>', buttonProps ).text( 'Delete this!' ).on( 'click', deleteEvent ); 
                    eventNodeButton.appendTo( eventNode ); 
                    eventNode.appendTo( eventsNode ); 
                } 
            } else { 
                var eventsNode = $( '' ); 
                eventsNode.text( 'No upcoming events found!' ); } 
                var $container = $( '#rest-events' ); 
                $container.empty(); 
                eventsNode.appendTo( $container ); 
            }
        };
             
        var showEvents = function() { 
            $.ajax( { 
                url: restTheme.root + 'events', 
                method: 'GET', 
                beforeSend: function( xhr ) {
                    xhr.setRequestHeader( 'X-WP-Nonce', restTheme.nonce ); 
                }, 
                data: { 'page': 1, 'per_page': 3, } 
            } ).done( renderEvents ); 
        }; 
            
        var deleteEvent = function() { 
            var $this = $( this ); 
            var eventId = $this.data( 'event-id' ); 
            if ( ! eventId ) { return; } 
            $.ajax( { 
                url: restTheme.root + 'events/' + eventId, 
                method: 'DELETE', 
                beforeSend: function( xhr ) { 
                    xhr.setRequestHeader( 'X-WP-Nonce', restTheme.nonce ); 
                }, 
                data: {} 
            } ).done( showEvents ); 
        }
        
        $( document ).ready( function() { 
            showEvents(); 
        } ); 
    } 
)( jQuery ) 

Now if we click the all-too-visible "Delete this!" button as a logged-out user, or as any user that cannot edit posts, nothing should happen.

Doing the same as a user that can edit posts will, instead, delete the events:

One-click client generation

Remember before, when, while talking about the Swagger.io format documentation, we mentioned being able to create clients for The Events Calendar REST API in one click?

Let's see how we can do that. First of all, head to the site /wp-json/tribe/events/v1/doc path on your site, and as before, copy the whole JSON format output. Head over to the Swagger.io Editor page:

Click the "Online Editor" button and paste the JSON output on the editor side (on the left):

If you are prompted to convert the JSON input into YAML, click "OK", count to 10, and you should see the documentation for The Events Calendar REST API appear on the right side.

Now click "Generate Client" on the upper menu, and wait while someone does the work for you:

At the end, you will get an archive file containing The Events Calendar REST API client code in the selected language.

You are welcome.