Timekit developers

3) Booking creation & management

Introduction

The booking engine is a hosted and flexible solution to handle booking flows for your app. It extends the functionality of the [POST] /events endpoint by adding structured meta data like customer info and enables conditional flows like confirm/decline. Furthermore, bookings follow a graph which can contain multiple steps and can trigger actions upon state change, like sending out emails or trigger webhooks.

Flow graphs

Graphs are the "blueprints" that bookings are based on. They define different actions and transitions that bookings can transition to, based on your business logic. Currently there's two graphs available:

  1. instant - Instant booking where new bookings are auto-confirmed
  2. confirm_decline - New bookings start in a tentative state and you can either confirm or decline it.

When you create a booking, you chose which flow graph it should follow. A JSON representation of the flow graphs can be retrieved by calling this endpoint and a Graphviz PNG representation using this.

Triggering actions

Actions are transitions between steps in your graph, such as confirming, cancelling or rescheduling a booking. Some actions are triggered manually by you through the API, where others are "autoplay" (internal steps) that happen automatically in a sequence. For instance, when you call the "confirm" action, the booking engine could automatically create a calendar event, send out an email notification and trigger a webhook before reaching the "confirmed" state.

When the first action is triggered, you have to supply event details and customer info too. The data supplied to the event parameter will be used to create an actual event in Timekit (internally calling [POST] /events) and the data in customer will be saved as a linked entity (only "name" and "email" is required).

If you specify the action parameter when creating a booking, that action will be triggered immediately. This saves you an additional [PUT] /bookings/:id/:action call.

All subsequent actions on bookings can be triggered using the [PUT] /bookings/:id/:action endpoint.

Action settings

Some actions have settings that you can pass to them, e.g. whether an "autoplay" action in the call-chain should be enabled or not (sending out emails, trigger webhooks etc). These actions take inputs through a key, which usually corresponds to the actions name. See graph references for an overview.

1. Creating a new booking

For this guide, we will create a booking based on the confirm_decline graph. Here's a diagram representation of how that looks:

Confirm/decline flow graph Confirm/decline flow graph

You can read an explanation here

Okay, let's get to it - start by calling [POST] /bookings with this:

curl
# Request example (replace :calendar-id)
# [POST] /bookings
curl -X POST \
     -H 'Timekit-App: docs' \
     -u doc.brown:::at:::timekit.io:FluxCapacitator \
     -d '{
          "graph": "confirm_decline",
          "action": "create",
          "event": {
            "start": "2015-03-01T08:00:00+00:00",
            "end": "2015-03-01T13:00:00+00:00",
            "what": "Mens haircut",
            "where": "Sesame St, Middleburg, FL 32068, USA",
            "calendar_id": ":calendar-id",
            "description": "Please arrive 10 minutes before you time begin"
          },
          "customer": {
            "name": "Marty McFly",
            "email": "marty.mcfly@timekit.io",
            "phone": "1-591-001-5403",
            "voip": "McFly",
            "timezone": "America/Los_Angeles"
          }
        }' \
     https://api.timekit.io/v2/bookings

JS SDK

javascript
// Request example
// [POST] /bookings

var timekit = require('timekit-sdk');

timekit.configure({
  app: 'docs',
  outputTimestampFormat: 'Y-m-d H:i:s'
});

timekit.setUser('doc.brown@timekit.io', 'FluxCapacitator');

timekit.createBooking({
  "graph": "confirm_decline",
  "action": "create",
  "event": {
    "start": "2015-03-01T08:00:00+00:00",
    "end": "2015-03-01T13:00:00+00:00",
    "what": "Mens haircut",
    "where": "Sesame St, Middleburg, FL 32068, USA",
    "calendar_id": ":calendar-id",
    "description": "Please arrive 10 minutes before you time begin"
  },
  "customer": {
    "name": "Marty McFly",
    "email": "marty.mcfly@timekit.io",
    "phone": "1-591-001-5403",
    "voip": "McFly",
    "timezone": "America/Los_Angeles"
  }
}).then(function(response) {
	console.log(response)
})

This will 1) create a new booking entity and 2) perform the "create" action on it immediately. The create action requires "event" and "customer" data, as it will save that to the Timekit DB.

You should get a response similar to this:

json
// Response example
// [POST] /bookings
{
  "data": {
    "id": "58190fc6-1ec0-4ebb-b627-7ce6aa9fc703",
    "graph": "confirm_decline",
    "state": "tentative",
    "completed": false,
    "possible_actions": [
      "decline",
      "confirm"
    ],
    "created_at": "2016-02-11T11:58:45+0100",
    "updated_at": "2016-02-11T11:58:47+0100",
    "attributes": {
      "event_info": {
        "start": "2015-03-01T08:00:00+00:00",
        "end": "2015-03-01T13:00:00+00:00",
        "what": "Mens haircut",
        "where": "Sesame St, Middleburg, FL 32068, USA",
        "description": "Please arrive 10 minutes before you time begin"
      }
    },
    "calendar": {
      "id": "c91c5d04-2a57-46c0-ab35-e489dadf132e",
      "name": "My calendar",
      "display_name": "My calendar",
      "description": "Ut adipisci non autem cum ut id.",
      "foregroundcolor": "#25d6be",
      "backgroundcolor": "#ea1cb8",
      "created_at": "2016-02-15T13:21:42+0100",
      "updated_at": "2016-02-15T13:21:42+0100"
    },
    "customers": [
      {
        "id": "a728e860-99c7-4009-8843-7d9ac5d7f53f",
        "name": "Marty McFly",
        "email": "marty.mcfly@timekit.io",
        "phone": "1-591-001-5403",
        "voip": "McFly",
        "timezone": "America/Los_Angeles"
      }
    ]
  }
}

The booking is now in the "tentative" state. If we take a look at the diagram again, we can see that it has performed the following autoplay actions:

  1. save_customer_data - validate and save the customer info provided in the "customer" key
  2. save_booking_data - validate and save the event info provided in the "event" key (this will be used for creating the calendar event later)
  3. send_confirm_decline_email_to_owner - sends out an email to the owner of the booking (service provider) based on a email template

The email sent looks something like this:

Notification email sent to owner Notification email sent to owner

The "Confirm" and "Decline" buttons currently links to a page in the new Timekit Admin that will call the relevant [PUT] /bookings/:id/:action endpoint when visited. It's a simple solution for those who just want to get quickly up and running (e.g. using Booking.js without a custom integration).

Some action steps has settings that you can pass to it in your request. E.g. the send_confirm_decline_email_to_owner step is optional and can be disabled by passing an action setting when creating the booking.

For now, we'll keep it enabled to prove the point. It's also worth mentioning that the template is currently hardcoded, but in the future, you could disable it and call an a webhook instead to handle emails yourself.

2A. Performing an action: decline

Let's assume that we're taking care of notifying our owners/providers ourselves and have our own custom admin panel where they can handle their bookings.

First you'll probably want to retrieve bookings by calling [GET] /bookings and list them.

Then let's assume the user clicks a "decline" button and provides a message (reason for cancelling) that you want to saved too. Call [PUT] /bookings/:id/decline like so:

curl
# Request example (replace :id)
# [PUT] /bookings/:id/decline
curl -X POST \
     -H 'Timekit-App: docs' \
     -u doc.brown@timekit.io:FluxCapacitator \
     -d '{
          "notify_customer_declined_by_email": {
				    "message": "Sorry, I'm not available at that location"
				  }
        }' \
     https://api.timekit.io/v2/bookings/:id/decline

JS SDK

javascript
// Request example
// [PUT] /bookings/:id/decline

var timekit = require('timekit-sdk');

timekit.configure({
  app: 'docs'
  outputTimestampFormat: 'Y-m-d H:i:s'
});

timekit.setUser(doc.brown@timekit.io, FluxCapacitator);

timekit.updateBooking({
  "notify_customer_declined_by_email": {
    "message": "Sorry, I'm not available at that location"
  }
}).then(function(response) {
	console.log(response)
})

This will trigger the decline steps as shown in the diagram and when it hits the notify_customer_declined_by_email step, it will use the message you provide in the request and add it to the email.

The resulting email will look something like this:

Notification email sent to customer Notification email sent to customer

Again, this email template will probably not fit your needs, so we can help customize the steps in your own custom flow.

2B. Performing an action: confirm

If we assumed that the owner wanted to confirm the booking instead, you would call [PUT] /bookings/:id/confirm and the booking would follow the steps in that branch.

The main action of interest here is the create_event. This will, as expected, create a Timekit event with the data provided in the "event" key when you created the booking (similar to calling [POST] /events). This plays nicely together with FindTime availability, as the event now blocks for the availability like you'd normally expect.

3. Retrieving booking data

The booking engine saves a complete audit trail of all actions and state changes performed throughout the lifetime of a booking. Call the [GET] /bookings to get all your bookings and their associated meta-data.

json
// Response example
// [GET] /bookings
{
  "data": {
    "id": "f68979ff-27da-4afd-8d0c-847f9340331b",
    "state": "declined",
    "graph": "confirm_decline",
    "completed": true,
    "possible_actions": [],
    "created_at": "2016-02-11T12:39:45+0100",
    "updated_at": "2016-02-11T13:12:01+0100",
    "attributes": {
      "event_info": {
        "start": "2015-01-01T09:00:00+0100",
        "end": "2015-01-01T14:00:00+0100",
        "what": "Mens haircut",
        "where": "Sesame St, Middleburg, FL 32068, USA",
        "description": "Please arrive 10 minutes before you time begin"
      }
    },
    "calendar": {
      "id": "c91c5d04-2a57-46c0-ab35-e489dadf132e",
      "name": "My calendar",
      "display_name": "My calendar",
      "description": "Ut adipisci non autem cum ut id.",
      "foregroundcolor": "#25d6be",
      "backgroundcolor": "#ea1cb8",
      "created_at": "2016-02-15T13:21:42+0100",
      "updated_at": "2016-02-15T13:21:42+0100"
    },
    "customers": [
      {
        "id": "a728e860-99c7-4009-8843-7d9ac5d7f53f",
        "name": "Marty McFly",
        "email": "marty.mcfly@timekit.io",
        "phone": "1-591-001-5403",
        "voip": "McFly",
        "timezone": "America/Los_Angeles"
      }
    ],
    "logs": [
      {
        "description": "Changed state to: created",
        "success": "true",
        "state": "created",
        "data": "[]",
        "created_at": "2016-02-11T12:39:45+0100"
      },
      {
        "description": "Changed state to: customer_data_saved",
        "success": "true",
        "state": "customer_data_saved",
        "data": "[]",
        "created_at": "2016-02-11T12:39:45+0100"
      },
      {
        "description": "Updated booking object with success",
        "success": "true",
        "state": "customer_data_saved",
        "data": "[]",
        "created_at": "2016-02-11T12:39:45+0100"
      },
      {
        "description": "Changed state to: booking_data_saved",
        "success": "true",
        "state": "booking_data_saved",
        "data": "[]",
        "created_at": "2016-02-11T12:39:45+0100"
      },
      {
        "description": "Trying to send (confirm/decline) email to owner (timebirdcphtest@gmail.com)...",
        "success": "true",
        "state": "booking_data_saved",
        "data": "[]",
        "created_at": "2016-02-11T12:39:45+0100"
      },
      {
        "description": "Email sent to timebirdcphtest@gmail.com!",
        "success": "true",
        "state": "booking_data_saved",
        "data": "[]",
        "created_at": "2016-02-11T12:39:46+0100"
      },
      {
        "description": "Changed state to: notified_owner_by_action_email",
        "success": "true",
        "state": "notified_owner_by_action_email",
        "data": "[]",
        "created_at": "2016-02-11T12:39:46+0100"
      },
      {
        "description": "Changed state to: tentative",
        "success": "true",
        "state": "tentative",
        "data": "[]",
        "created_at": "2016-02-11T12:39:46+0100"
      },
      {
        "description": "Booking was rejected by: timebirdcphtest@gmail.com",
        "success": "true",
        "state": "tentative",
        "data": "[]",
        "created_at": "2016-02-11T13:11:59+0100"
      },
      {
        "description": "Changed state to: declining",
        "success": "true",
        "state": "declining",
        "data": "[]",
        "created_at": "2016-02-11T13:11:59+0100"
      },
      {
        "description": "Trying to send (declined notification) email to customer (marty.mcfly@timekit.io)...",
        "success": "true",
        "state": "declining",
        "data": "[]",
        "created_at": "2016-02-11T13:11:59+0100"
      },
      {
        "description": "Email sent to marty.mcfly@timekit.io!",
        "success": "true",
        "state": "declining",
        "data": "[]",
        "created_at": "2016-02-11T13:12:01+0100"
      },
      {
        "description": "Changed state to: notified_customer_declined_by_email",
        "success": "true",
        "state": "notified_customer_declined_by_email",
        "data": "[]",
        "created_at": "2016-02-11T13:12:01+0100"
      },
      {
        "description": "Changed state to: declined",
        "success": "true",
        "state": "declined",
        "data": "[]",
        "created_at": "2016-02-11T13:12:01+0100"
      }
    ]
  }
}

Great scott, you've reached the end!