Elevate the quality of your products and streamline your operational workflows by leveraging the seamless integration capabilities of the Demodesk API. With its holistic integration, Demodesk is designed to easily blend with your existing products and workflows, boosting your operational efficiency to new heights. Unleash the synergy of Demodesk and your products and redefine what is possible in operational excellence.
Highlights
Effortless Scheduling and User Creation: With the Demodesk API, automating tasks such as scheduling meetings or creating user accounts becomes a breeze. While users continue to engage with the intuitive Demodesk dashboard, the API works silently in the background, streamlining operations and enhancing user experience.
Complete White-Label Integration:
Your brand, front and center: With Demodesk's white-label solution, your brand stays in the spotlight. Our platform seamlessly merges with your digital ecosystem, ensuring that your customers interact solely with your branded experience.
Your Domain, Your Meeting Space: Deploy a full white-label solution on your domain (e.g., meet.yourdomain.com) and provide a personalized, branded meeting space for your customers, further enhancing trust and brand cohesion.
Simplified Logins: Say goodbye to the hassle of managing multiple logins. Our integration ensures a synchronized login experience with your host application, providing a streamlined access point for users.
Your custom Dashboard: Keep the controls and build your own dashboard in your application. Our API will handle all the interactions in the background.
API Reference
Company and User Management
Create account
To create an account, follow these steps:
Create account
Provide company details
Add users to the company
curl -X "POST" "https://demodesk.com/api/v1/auth" \
-H 'api-key: <MASTER_3RD_PARTY_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"confirmSuccessUrl": "confirmed",
"password": "passwordtologintodemodeskdashboard",
"firstName": "Stan",
"timeZone": "Europe/Berlin",
"email": "example@testco.com",
"promoCodeToken": "<PROMO_CODE>",
"lastName": "Test",
"locale: "en"
}'
Request:
Password: you can set a password here in case you want to manually login to the Demodesk dashboard.
PromoCodeToken: pass the code you received in the partnership agreement. This will activate the floating license functionality for the account, set the appropriate trial duration and exclude this company from any Demodesk marketing campaign.
Response:
apiKey: this API key is the master key for the company account
COMPANY_ADMIN_USER_API_KEY
status:
ok
orerror
(seeerrors[0].details
for explanation)
Create company
curl -X "POST" "https://demodesk.com/api/v1/companies" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"data": {
"attributes": {
"name": "Test Co",
"countryCode": "DE",
"timeZone": "Europe/Berlin"
}
}
}'
Response:
id: id of newly created company
status:
ok
orerror
(seeerrors[0].details
for explanation)
Get users of a company
curl "https://demodesk.com/api/v1/companies/<company_id>/users" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY>'
Add users to company
curl -X "POST" "https://demodesk.com/api/v1/users" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"data": {
"type": "users",
"attributes": {
"firstName": "First",
"email": "first.last@testco.com",
"lastName": "Last",
"locale": "en"
}
}
}'
Response:
id: id of the newly created user
apiKey: user's API key. Use this one to request auth tokens from the Demodesk backend to login this specific user.
status:
ok
orerror
(seeerrors[0].details
for explanation)
Update user data
curl -X "PATCH" "https://demodesk.com/api/v1/users/<user_id>" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"data": {
"id": "<user_id>",
"type": "users",
"attributes": {
"firstName": "First",
"email": "first.last@testco.com",
"lastName": "Last"
}
}
}'
Response:
status:
ok
orerror
(seeerrors[0].details
for explanation)
Delete user
curl -X "DELETE" "https://demodesk.com/api/v1/users/<user_id>" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>'
Response:
status:
ok
orerror
(seeerrors[0].details
for explanation)
Meeting management
Create meetings
curl -X "POST" "https://demodesk.com/api/v1/scheduled_demos" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"data": {
"type": "demos",
"attributes": {
"account": "Test Account" # Meeting Name,
"timeZone": "Berlin",
"urls": [
"https://google.com",
"https://dict.cc"
],
"startDate": "2020-05-25T11:30:00.000+01:00",
"countryCode": "US",
"duration": 1800,
"user_id": "<user_id>",
"setup_phone": "full",
}
}
}'
Response:
id: meeting id
status:
ok
orerror
(seeerrors[0].details
for explanation)
Get meeting details
curl "https://demodesk.com/api/v1/scheduled_demos/<demo_token>"
Response:
id: meeting id
attributes.status: one of
scheduled
,starting
,started
,ending
,ended
,canceled
status:
ok
orerror
(seeerrors[0].details
for explanation)
Example response:
{
"data": {
"id": "<demo_id>",
"type": "demos",
"attributes": {
"status": "ended",
"duration": 3600,
"locale": "nl",
"countryCode": "NL",
"timeZone": "Europe/Amsterdam",
"account": "Example account",
"setupPhone": "full",
"twilioPhone": null,
"token": "PYBGFKMZ",
"createdAt": "2020-08-25T14:25:49.579Z",
"updatedAt": "2020-09-02T12:56:02.451Z",
"startDate": "2020-09-02T11:00:00.000+02:00",
"link": "https://demodesk.com/XXXXXXXX",
"controlUrl": "https://aws-eu-central-1.demodesk.com/api/v1/renderers/50dab44f-ecdb-4b12-8831-b0fccd608e26",
},
"relationships": {
"user": {
"data": {
"id": "<user_id>",
"type": "users"
}
},
"participants": {
"data": [
{
"id": "<participant_id>",
"type": "participants"
}
]
},
}
},
"included": [
{
"id": "<participant_id>",
"type": "participants",
"attributes": {
"kind": "browser",
"firstName": "Evi",
"lastName": null,
"fullName": "Evi",
},
},
],
"status": "ok"
}
List meetings
curl -X "GET" "https://demodesk.com/api/v1/demos?page=1&filter[schedule_eq]=upcoming&filter[all_team_members_dashboard]=true" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>' \
-H 'Content-Type: application/json'
Parameters (optional):
More on search matchers for filters are documented here.
filter[schedule_eq] can be
past
orupcoming
, ie. show only past or upcoming meetingsfilter[all_team_members_dashboard] can be
true
orfalse
, ie. whether to show meetings only for the API key user or for the full teamfilter[account_i_cont] string that filters for the meeting name
filter[start_date_gteq] meeting happened after date, e.g.
2022-10-10
filter[start_date_lteq] meeting happened before date, e.g.
2022-10-13
filter[group_id_eq] filters for meetings from group. Needs the group id.
filter[recordings_present] can be
true
orfalse
, ie. whether the meeting was/is going to be recordedfilter[template_name_i_cont] filters for event type name
page is the requested page number, starting with
1
. This endpoint is paginated, the total number of pages can be found in response→meta→totalPages
Response:
data: array of matching meetings
id: meeting id
attributes.status: one of
scheduled
,starting
,started
,ending
,ended
,canceled
status:
ok
orerror
(seeerrors[0].details
for explanation)meta: pagination information
Example response:
{
"data": [
{
"id": "123",
"type": "demos",
"attributes": {
"status": "started",
"duration": 1800,
"account": "Instant meeting",
"link": "https://demodesk.com/ABCDEFGH",
"token": "ABCDEFGH",
"userFirstName": "John",
"userLastName": "Doe",
"startDate": "2022-10-17T15:55:37.286+01:00",
},
"relationships": {
"user": {"data": {"id": "9773","type": "users"}},
"booker": {"data": {"id": "9773","type": "users"}},
"participants": {"data": [{"id": "3427341","type": "participants"}]},
"demoType": {"data": {"id": "5842","type": "demoTypes"}}, // playbook
"demoTemplate": {"data": {"id": "7194","type": "demoTemplates"}}, // event type
"recordings": {"data": []},
"scheduledShadowUsers": {"data": []} // invited shadows
}
},
{
// next demo
}
],
"included": [
// includes all objects that are references in "relationships"
],
"status": "ok",
"meta": {
"totalCount": 668,
"pageSize": 25,
"totalPages": 27,
"currentPage": 1
}
}
Update meeting
curl -X "PATCH" "https://demodesk.com/api/v1/scheduled_demos/<demo_id>" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>' \
-H 'Content-Type: application/json' \
-d $'{
"data": {
"type": "demos",
"id": "<demo_id>",
"attributes": {
"account": "Updated account name"
}
}
}'
Response:
id: meeting id
status:
ok
orerror
(seeerrors[0].details
for explanation)
Cancel meeting
curl -X "POST" "https://demodesk.com/api/v1/scheduled_demos/<demo_id>/cancel" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>'
Response:
id: meeting id
status:
ok
orerror
(seeerrors[0].details
for explanation)
Delete meeting
curl -X "DELETE" "https://demodesk.com/api/v1/scheduled_demos/<demo_id>" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>'
Response:
status:
ok
orerror
(seeerrors[0].details
for explanation)
Recordings
Access recording transcript
curl "https://demodesk.com/api/v1/recordings/<RECORDING_TOKEN>/transcript" \
-H 'api-key: <COMPANY_ADMIN_USER_API_KEY> or <USER_API_KEY>' \
-H 'Accept: text/plain'
Error codes
Error responses are formatted in the following way:
{
"errors": [
{
"detail": "Error 1"
},
{
"detail": "Error 2"
}
],
"status": "not_found"
}
These errors are commonly used by all endpoints:
404
,not_found
: The requested resource was not found422
,unprocessable_entity
: The sent update request was invalid. Check body for details.403
,forbidden
:Error: 'You are not authorized to perform this action.'
You don't have the required authorization to perform the requested action401
,unauthorized
:Error: 'The provided API key is invalid.'
403
,forbidden
:Error: 'Illegal association id update.'
400
,bad_request
: Malformed request. Check body for detailed error message.
Webhook API
Please email alex@demodesk.com with the endpoints you want to be called for each event.
It’s possible to listen to the following webhook events from Demodesk:
'demo.scheduled'
'demo.rescheduled'
'demo.handovered'
'demo.canceled'
'demo.started'
'demo.ended'
'recording.uploaded'
'recording.transcription_postprocessed'
Example response:
{
"meta": {
"event": {
"action": "demo.started",
"apiVersion": "v1",
"createdAt": "2022-03-18T14:56:29.022Z"
}
},
"data": {
"id": "1234",
"type": "demos",
"attributes": {
"token": "ABCDEFGH",
"link": "https://demodesk.com/ABCDEFGH",
"location": "https://demodesk.com/meet/alexander-popp",
"name": "Meeting name",
"createdVia": "calendar_backsync",
"createdAt": "2022-03-14T17:55:33.000Z",
"updatedAt": "2022-03-18T14:56:26.000Z",
"hostEmail": "alex@demodesk.com",
"playbookName": "Playbook name",
"eventTypeName": "Event type name",
"tokens": {
"demoNotes": "Great lead. Should follow up next month",
"companySizeCustomToken": "100 - 200"
}
}
}
}
Terminology
3rd Party Backend: Your server, used for fetching authentication tokens from the Demodesk backend.
3rd Party Frontend: Your frontend code that implements the
DemodeskAuth
library.Demodesk Backend: Demodesk’s backend, controls authentication and issues auth tokens.
Demodesk Frontend: Demodesk’s frontend, hosts the meeting dashboard.
Authentication
Authentication is managed through three types of API keys:
MASTER_3RD_PARTY_API_KEY: Your secret partnership API key. Use this to create new accounts.
COMPANY_ADMIN_USER_API_KEY: The master API key for each company. Administer individual company accounts with this key.
USER_API_KEY: Individual user API key. Administer individual user accounts with this key.
Login Flow
DemodeskAuth
library sends a request to the 3rd party backend to request an auth token.3rd party backend forwards the request to Demodesk backend.
Demodesk backend responds with an auth token.
DemodeskAuth
library writes authorization data to browser storage.
Implementation
DemodeskAuth JS
Get the demodesk-auth.js
file from the Appendix and include DemodeskAuth in the 3rd party frontend and initialize it:
var auth = new DemodeskAuth({
foreignHost: 'yourdomain.com',
meetingHost: 'meet.yourdomain.com',
fetchAuthToken: function() {
return fetch('https://yourdomain.com/api/demodesk_auth') // this is the auth forward endpoint
.then(response => response.json())
.then(json => json.authToken);
},
});
Auth forward endpoint
In the 3rd party backend, create an endpoint that accepts and responds in the following format:
curl -X "POST" "https://demodesk.com/api/v1/users/login" \
-H 'api-key: <USER_API_KEY>'
The response should have a structure like this:
{
"status": "ok",
"meta": {
"access_token": "eyJhY2Nlc3NfdG9rZW4iOiIyTUx0RV9xUEdLblRkRHlVMFpBQndRIiwiY2xpZW50IjoidkFSVzRKc2VXYnM2a0VkZTV5V3RuZyIsImV4cGlyeSI6MTU5MzI1NTU0MiwidG9rZW5fdHlwZSI6IkJlYXJlciIsInVpZCI6ImFsZXhAZGVtb2Rlc2suY29tIiwicmVxdWVzdGVkX2F0IjoiMjAyMC0wNi0xM1QxMDo1OTowMloifQ=="
}
}
Appendix
demodesk-auth.js
demodesk-auth.js
DemodeskAuth = (function(){
var C = function(){ return constructor.apply(this,arguments); }
const AUTH_TOKEN_CACHE_KEY = 'demodesk.authTokenCache';
const MIN_TOKEN_EXPIRY_GRACE_DURATION = 7 * 24 * 60 * 60; // 7 days (max validity is 14 days)
var p = C.prototype;
p.foreignHost; // 'yourdomain.com';
p.meetingHost; // 'meet.yourdomain.com';
p.fetchAuthToken = () => void 0; // Function to fetch auth token from 3rd party backend
p.onAuthenticated = () => void 0; // Callback function to signal successful authentication
function constructor(options) {
this.foreignHost = options.foreignHost;
this.meetingHost = options.meetingHost;
this.fetchAuthToken = options.fetchAuthToken;
this.onAuthenticated = options.onAuthenticated;
}
// External functions
p.authenticate = function() {
if (isLastAuthTokenValid()) {
console.log('Last auth token is still valid. Doing nothing.');
} else {
console.log('Last auth token is expired. Fetching fresh one.');
writeFreshAuthToken(this);
}
}
p.logout = function() {
removeCachedAuthToken();
removeDemodeskCookies(this);
}
// Internal functions
async function writeFreshAuthToken(context) {
const token = await context.fetchAuthToken();
const bearerToken = parseAuthToken(token);
writeDemodeskCookies(bearerToken, context);
setCachedAuthToken(token);
}
function writeDemodeskCookies(bearerToken, context) {
var frameStyle = {
display: 'none',
position: 'absolute',
top: '-999px',
left: '-999px'
};
frame = window.document.createElement('iframe');
frame.id = 'demodesk-auth-iframe';
for (key in frameStyle) {
frame.style[key] = frameStyle[key];
}
frame.onload = function() {
setTimeout(() => {
context.onAuthenticated();
frame.remove();
}, 200);
};
window.document.body.appendChild(frame);
frame.src = "https://" + context.meetingHost + "/manage/static-user/remote-login.html?" +
"access_token=" + bearerToken['access_token'] +
"&client=" + bearerToken['client'] +
"&expiry=" + bearerToken['expiry'] +
"&token_type=" + bearerToken['token_type'] +
"&uid=" + encodeURIComponent(bearerToken['uid']) +
"&requested_at=2030-06-15T07:42:37Z" +
"&viewer_access=true";
}
function removeDemodeskCookies(context) {
var frameStyle = {
display: 'none',
position: 'absolute',
top: '-999px',
left: '-999px'
};
frame = window.document.createElement('iframe');
frame.id = 'demodesk-auth-iframe';
for (key in frameStyle) {
frame.style[key] = frameStyle[key];
}
frame.onload = function() {
setTimeout(() => {
frame.remove();
}, 200);
};
window.document.body.appendChild(frame);
frame.src = "https://" + context.meetingHost + "/manage/static-user/remote-logout.html"
}
function isLastAuthTokenValid() {
const currentTime = Math.floor(Date.now() / 1000);
console.log(`Current time: ${currentTime}`);
const tokenTime = getLastAuthTokenExpiry();
console.log(`Token time: ${tokenTime}`);
const remainingTokenDuration = tokenTime - currentTime;
console.log(`Remaining token duration: ${remainingTokenDuration}`);
return remainingTokenDuration >= MIN_TOKEN_EXPIRY_GRACE_DURATION;
}
function getLastAuthTokenExpiry() {
const cachedToken = getCachedAuthToken();
const parsedToken = parseAuthToken(cachedToken);
return parsedToken['expiry'];
}
function getCachedAuthToken() {
return localStorage.getItem(AUTH_TOKEN_CACHE_KEY);
}
function setCachedAuthToken(authToken) {
localStorage.setItem(AUTH_TOKEN_CACHE_KEY, authToken);
console.log('New auth token stored.');
}
function removeCachedAuthToken() {
localStorage.removeItem(AUTH_TOKEN_CACHE_KEY);
}
function parseAuthToken(authToken) {
const decodedString = atob(authToken);
return JSON.parse(decodedString);
}
return C;
})();
FAQs
Why do we receive multiple demoEnded webhooks with different demoStatus values for the same demo?
Why do we receive multiple demoEnded webhooks with different demoStatus values for the same demo?
This can occur when a meeting is manually ended and then restarted. When the meeting is first ended, a demoEnded webhook is triggered with a specific demoStatus (e.g., "no-show"). If the meeting is restarted, another demoEnded webhook is sent with a different demoStatus, reflecting the updated state of the demo. This behavior is expected when a meeting is manually stopped and restarted.
Can a demo be canceled after it has started?
Can a demo be canceled after it has started?
No, once a demo has started, it cannot be canceled via the API. If a customer cancels their appointment shortly before the scheduled start time, but the demo has already been triggered (e.g., via the demo.started
webhook), it is no longer possible to cancel the demo. In such cases, the host's calendar remains blocked and the appointment must be removed manually. This behavior is intended to prevent issues with state handling, such as shutting down a demo that hasn't fully started or when the renderer has not yet been assigned.