├── examples ├── config.sample.json ├── custom-sidebar │ ├── sidebar.html │ ├── index.js │ └── index.html ├── oauth2 │ ├── index.html │ ├── oauth2.html │ └── index.js ├── basic │ ├── index.html │ └── index.js ├── webhooks │ ├── index.js │ └── index.html ├── utils.js └── create-appointment │ ├── index.html │ └── index.js ├── .gitignore ├── package.json ├── LICENSE ├── src ├── AcuityScheduling.js ├── AcuitySchedulingOAuth.js └── index.js └── README.md /examples/config.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "userId": 1, 3 | "apiKey": "abc123" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | examples/oauth2/config.json 3 | examples/config.json 4 | -------------------------------------------------------------------------------- /examples/custom-sidebar/sidebar.html: -------------------------------------------------------------------------------- 1 |
{{details}}
3 |
--------------------------------------------------------------------------------
/examples/oauth2/index.html:
--------------------------------------------------------------------------------
1 | {{query}}
5 |
6 | {{#if query.error}}
7 | An error has occurred: ' + query.error + '.
' 8 | {{else}} 9 |
{{tokenResponse}}
11 |
12 | {{me}}
14 | {{/if}}
15 |
--------------------------------------------------------------------------------
/examples/basic/index.html:
--------------------------------------------------------------------------------
1 | 4 | Hi there! This is the Hello World of our examples. It will walk you through 5 | a basic connection to the Acuity API with a single account. 6 |
7 | 8 |{{me}}
10 |
11 | {{block}}
13 |
14 | {{appointments}}
16 |
--------------------------------------------------------------------------------
/examples/webhooks/index.js:
--------------------------------------------------------------------------------
1 | // Deps
2 | var express = require('express');
3 | var config = require('../config');
4 | var utils = require('../utils');
5 | var Acuity = require('../../');
6 |
7 |
8 | // App:
9 | var app = express();
10 |
11 |
12 | // Verification middleware for the webhook route
13 | var secret = config.apiKey;
14 | var verifyMiddleware = express.urlencoded({
15 | verify: Acuity.bodyParserVerify(secret)
16 | });
17 |
18 |
19 | // Router:
20 | app.get('/', function (req, res) {
21 | res.render('index.html');
22 | });
23 |
24 | app.post('/webhook', verifyMiddleware, function (req, res) {
25 | // The message is authentic:
26 | console.log("The message is authentic:\n" + JSON.stringify(req.body, null, ' '));
27 | res.send('');
28 | });
29 |
30 |
31 | // Server:
32 | utils.configure(app, {views: __dirname});
33 | var server = utils.start(app);
34 |
--------------------------------------------------------------------------------
/examples/custom-sidebar/index.js:
--------------------------------------------------------------------------------
1 | // Deps
2 | var express = require('express');
3 | var config = require('../config');
4 | var utils = require('../utils');
5 | var Acuity = require('../../');
6 |
7 |
8 | // App:
9 | var app = express();
10 |
11 |
12 | // Verification middleware for the webhook route
13 | var secret = config.apiKey;
14 | var verifyMiddleware = express.urlencoded({
15 | verify: Acuity.bodyParserVerify(secret)
16 | });
17 |
18 |
19 | // Router:
20 | app.get('/', function (req, res) {
21 | res.render('index.html');
22 | });
23 |
24 | app.post('/custom-sidebar', verifyMiddleware, function (req, res) {
25 | setTimeout(function () {
26 | res.render('sidebar.html', {
27 | details: JSON.stringify(req.body, null, ' ')
28 | });
29 | }, 500);
30 | });
31 |
32 |
33 | // Server:
34 | utils.configure(app, {views: __dirname});
35 | var server = utils.start(app);
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "acuityscheduling",
3 | "version": "0.1.9",
4 | "description": "Acuity Scheduling JS Dev Kit",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Carl Sutherland
4 | Acuity will post to /custom-sidebar when appointment details are
5 | viewed and display the result in the appointment details sidebar.
6 |
9 | Acuity won't be able to access 127.0.0.1:8000 so you'll either
10 | have to take our word for it, or try this on your server! Set your callback
11 | location under the
12 | Custom Sidebar integration.
13 |
18 | All arguments are sent as application/x-www-form-urlencoded content.
19 |
4 | Acuity will post to /webhook when a new appointment is scheduled
5 | or an existing appointment is rescheduled or canceled.
6 |
9 | Acuity won't be able to access 127.0.0.1:8000 so you'll either
10 | have to take our word for it, or try this on your server! Set your callback
11 | location under the
12 | Webhook integration.
13 |
18 | All arguments are sent as application/x-www-form-urlencoded content.
19 |
scheduled rescheduled or canceled depending on the action that initiated the webhook call4 | This example walks you through booking an appointment with our create-appointment API. 5 | First we fetch appointment types and prompt the user to choose one. Using the 6 | selected appointment type, we check available dates and 7 | times from the API and ask the user to choose. Finally, basic client 8 | information is collected and we create the appointment. 9 |
10 | 11 | {{#if appointment}} 12 |
14 | {{appointment}}
15 |
16 | Start Over
17 | {{else}}
18 |
19 |
80 |
81 | {{/if}}
82 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Acuity lib
3 | */
4 |
5 | var AcuityScheduling = require('./AcuityScheduling');
6 | var AcuitySchedulingOAuth = require('./AcuitySchedulingOAuth');
7 | var querystring = require('querystring');
8 | var crypto = require('crypto');
9 |
10 | var acuity = {
11 |
12 | basic: function (config) {
13 | return new AcuityScheduling(config);
14 | },
15 |
16 | oauth: function (config) {
17 | return new AcuitySchedulingOAuth(config);
18 | },
19 |
20 | verifyMessageSignature: function (secret, body, signature) {
21 |
22 | if (!secret || typeof secret !== 'string') {
23 | throw new Error('Verify the message signature using your API key as the secret.');
24 | }
25 |
26 | // Get hash of message using shared secret:
27 | var hasher = crypto.createHmac('sha256', secret);
28 | hasher.update(body);
29 | var hash = hasher.digest('base64');
30 |
31 | // Compare hash to Acuity signature:
32 | if (hash !== signature) {
33 | throw new Error('This message was forged!');
34 | }
35 | },
36 |
37 | bodyParserVerify: function (secret) {
38 | return function (req, res, buf, encoding) {
39 | var body = buf.toString();
40 | var signature = req.headers['X-Acuity-Signature'.toLowerCase()];
41 | acuity.verifyMessageSignature(secret, body, signature);
42 | };
43 | },
44 |
45 | /**
46 | * Generate embed code for $owner.
47 | *
48 | * @param {number} owner The owner's id.
49 | * @param {object} options Additional options.
50 | * - width Iframe width
51 | * - height Iframe height
52 | * - query Query string arguments
53 | */
54 | getEmbedCode: function (owner, options) {
55 |
56 | options = Object.create(options || {});
57 | options.height = options.height || '800';
58 | options.width = options.width || '100%';
59 |
60 | var query = options.query = options.query || {};
61 | query.owner = query.owner || owner;
62 |
63 | // Encode options:
64 | for (key in options) {
65 | if (key === 'query') {
66 | options[key] = querystring.stringify(options[key]);
67 | } else {
68 | options[key] = escape(options[key]);
69 | }
70 | }
71 |
72 | return '' +
73 | '' +
74 | '';
75 | }
76 | };
77 |
78 | /**
79 | * Escape HTML entities
80 | *
81 | * Escape function borrowed from Mustache.
82 | */
83 | var enitites = {
84 | "&": "&",
85 | "<": "<",
86 | ">": ">",
87 | '"': '"',
88 | "'": ''',
89 | "/": '/'
90 | };
91 | function escape (s) {
92 | return (s + '').replace(/[&<>"'\/]/g, function (c) {
93 | return entities[c];
94 | });
95 | }
96 |
97 | module.exports = acuity;
98 |
--------------------------------------------------------------------------------
/examples/create-appointment/index.js:
--------------------------------------------------------------------------------
1 | // Deps:
2 | var config = require('../config');
3 | var Acuity = require('../../');
4 | var utils = require('../utils');
5 |
6 |
7 | // App:
8 | var app = utils.express({views: __dirname});
9 |
10 |
11 | // Router:
12 | app.get('/', function (request, response) {
13 | var acuity = Acuity.basic(config);
14 | var session = request.session;
15 |
16 | session.appointmentTypes = null;
17 | session.appointmentTypeID = null;
18 | session.date = null;
19 | session.time = null;
20 |
21 | response.render('index.html', {
22 | start: true
23 | });
24 | });
25 |
26 | app.post('/', function (request, response) {
27 |
28 | var body = request.body;
29 | var acuity = Acuity.basic(config);
30 | var session = request.session;
31 | var appointmentType = null;
32 | var appointmentTypes = session.appointmentTypes || null;
33 | var appointmentTypeID = session.appointmentTypeID = body.appointmentTypeID || session.appointmentTypeID || null;
34 | var date = session.date = body.date || session.date || null;
35 | var time = session.time = body.time || session.time || null;
36 |
37 | // First fetch possible appointment types:
38 | if (!appointmentTypes) {
39 | return acuity.request('/appointment-types', function (err, res, appointmentTypes) {
40 | request.session.appointmentTypes = appointmentTypes;
41 | response.render('index.html', {
42 | appointmentTypes: appointmentTypes
43 | });
44 | });
45 | }
46 |
47 | // Grab the selected appointment type:
48 | appointmentTypes.forEach(function (type) {
49 | if (type.id == appointmentTypeID) {
50 | appointmentType = appointmentType = type;
51 | }
52 | });
53 |
54 | // Appointment type id:
55 | if (!appointmentType) {
56 | return response.render('index.html', {
57 | appointmentTypes: appointmentTypes
58 | });
59 | }
60 |
61 | // Date:
62 | if (!date) {
63 | var month = new Date();
64 | month = month.getFullYear() + '-' + (month.getMonth() + 1);
65 | return acuity.request('/availability/dates?month=' + month + '&appointmentTypeID=' + appointmentType.id, function (err, res, dates) {
66 | response.render('index.html', {
67 | appointmentType: appointmentType,
68 | dates: dates
69 | });
70 | });
71 | }
72 |
73 | // Time:
74 | if (!time) {
75 | return acuity.request('/availability/times?date=' + date + '&appointmentTypeID=' + appointmentType.id, function (err, res, times) {
76 | response.render('index.html', {
77 | appointmentType: appointmentType,
78 | date: date,
79 | times: times
80 | });
81 | });
82 | }
83 |
84 | // Client info:
85 | if (!body.firstName || !body.lastName || !body.email) {
86 | return response.render('index.html', {
87 | appointmentType: appointmentType,
88 | date: date,
89 | time: time
90 | });
91 | }
92 |
93 | // Create appointment:
94 | var options = {
95 | method: 'POST',
96 | body: {
97 | appointmentTypeID: appointmentTypeID,
98 | datetime: time,
99 | firstName: body.firstName,
100 | lastName: body.lastName,
101 | email: body.email
102 | }
103 | };
104 | return acuity.request('/appointments', options, function (err, res, appointment) {
105 | response.render('index.html', {
106 | appointment: JSON.stringify(appointment, null, ' ')
107 | });
108 | });
109 | });
110 |
111 |
112 | // Server:
113 | var server = utils.start(app);
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Acuity Scheduling API - JS Tool Kit
2 |
3 | Welcome to the Acuity Scheduling JS SDK. This SDK provides examples and a standard library for integrating with the [Acuity Scheduling API](https://acuityscheduling.com/) using JS. You can learn more about developing for Acuity Scheduling at [developers.acuityscheduling.com](https://developers.acuityscheduling.com/).
4 |
5 | ## Installation
6 |
7 | This package can be installed for node using npm:
8 |
9 | ```sh
10 | $ npm install --save acuityscheduling
11 | ```
12 |
13 | Then require it in your app:
14 |
15 | ```js
16 | var Acuity = require('acuityscheduling');
17 | ```
18 |
19 | Currently our API is only for server-side access and our SDK won't work on the client-side.
20 |
21 | ## Hello World
22 |
23 | Here's a basic example to get started. Just set your API credentials and run:
24 |
25 | ```js
26 | var Acuity = require('acuityscheduling');
27 | var userId = null;
28 | var apiKey = null;
29 |
30 | var acuity = Acuity.basic({
31 | userId: userId,
32 | apiKey: apiKey
33 | });
34 |
35 | acuity.request('/appointments', function (err, res, appointments) {
36 | if (err) return console.error(err);
37 | console.log(appointments);
38 | });
39 | ```
40 |
41 | ## Examples
42 |
43 | You'll find several examples of different Acuity integrations in the [examples/](examples/) directory. These examples cover:
44 | * [Basic API Access](#basic-api-access)
45 | * [OAuth2 API Access](#oauth2-api-access)
46 | * [Webhooks](#webhooks)
47 | * [Custom Sidebar](#custom-sidebar)
48 |
49 | ##### Sample `examples/config.json`
50 |
51 | Create a config file with your API credentials to get started. All examples
52 | share a common config file containing your Acuity `userId` and `apiKey` for basic API access and verifying callbacks. [OAuth2 examples](#oauth2-api-access) require
53 | additional OAuth2 client account credentials.
54 |
55 | ```json
56 | {
57 | "userId": 1,
58 | "apiKey": "abc123"
59 | }
60 | ```
61 |
62 | ### Basic API Access
63 |
64 | [examples/basic/](examples/basic) is a basic API integration for a single account.
65 |
66 | Start the example server by doing `PORT=8000 node examples/basic/index.js` and navigate to 127.0.0.1:8000
67 |
68 | ### Create an Appointment
69 |
70 | [examples/create-appointment/](examples/create-appointment) is a more advanced API example for scheduling an appointment. In this example, you'll see how to:
71 |
72 | * fetch appoinment types
73 | * find an available date and time
74 | * create the appointment
75 |
76 | Start the example server by doing `PORT=8000 node examples/create-appointment/index.js` and navigate to 127.0.0.1:8000
77 |
78 | ### OAuth2 API Access
79 |
80 | [examples/oauth2/](examples/oauth2) is an OAuth2 API integration. Use this to get connected with multiple Acuity accounts.
81 |
82 | Create a config file with your OAuth2 credentials to get started. If you don't have OAuth2 credentials, please fill out this registration form.
83 | Start the example server by doing `PORT=8000 node examples/oauth2/index.js` and navigate to 127.0.0.1:8000
84 |
85 | ##### Sample `examples/config.json`
86 | ```json
87 | {
88 | "clientId": "N4HgVZbjHVp3HAkR",
89 | "clientSecret": "H33vYz88sEiKVbl7EMob1URDrqZrvceSCMmZJpAi",
90 | "redirectUri": "http://127.0.0.1:8000/oauth2"
91 | }
92 | ```
93 |
94 | ### Webhooks
95 |
96 | [examples/webhooks/](examples/webhooks) is a sample webhook integration.
97 |
98 | Start the example server by doing `PORT=8000 node examples/webhooks/index.js` and navigate to 127.0.0.1:8000
99 |
100 | ### Custom Sidebar
101 |
102 | [examples/custom-sidebar/](examples/custom-sidebar) allows you to display custom information in the appointment details sidebar.
103 |
104 | Start the example server by doing `PORT=8000 node examples/custom-sidebar/index.js` and navigate to 127.0.0.1:8000
105 |
--------------------------------------------------------------------------------