curl --request POST \
--url https://api.cal.com/v2/discover/booking-jobs \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"businessUrl": "https://barbers.example/book",
"service": "haircut",
"area": "Capitol Hill, Seattle",
"daysFromToday": 1,
"partOfDay": "afternoon",
"idempotencyKey": "4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10",
"budgetDollars": 60
}
'import requests
url = "https://api.cal.com/v2/discover/booking-jobs"
payload = {
"businessUrl": "https://barbers.example/book",
"service": "haircut",
"area": "Capitol Hill, Seattle",
"daysFromToday": 1,
"partOfDay": "afternoon",
"idempotencyKey": "4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10",
"budgetDollars": 60
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
businessUrl: 'https://barbers.example/book',
service: 'haircut',
area: 'Capitol Hill, Seattle',
daysFromToday: 1,
partOfDay: 'afternoon',
idempotencyKey: '4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10',
budgetDollars: 60
})
};
fetch('https://api.cal.com/v2/discover/booking-jobs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cal.com/v2/discover/booking-jobs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'businessUrl' => 'https://barbers.example/book',
'service' => 'haircut',
'area' => 'Capitol Hill, Seattle',
'daysFromToday' => 1,
'partOfDay' => 'afternoon',
'idempotencyKey' => '4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10',
'budgetDollars' => 60
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cal.com/v2/discover/booking-jobs"
payload := strings.NewReader("{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cal.com/v2/discover/booking-jobs")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cal.com/v2/discover/booking-jobs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"created": true,
"jobId": "cm9x1booking0job",
"jobUrl": "https://app.cal.com/discover/book/cm9x1booking0job",
"reason": "The assistant cannot book online for this account.",
"signInRequired": true,
"paymentRequired": true,
"planUrl": "https://app.cal.com/settings/discover/preferences"
}
}Book a web-found business online
Has Cal’s booking agent book a service on a business’s own online booking page for the user, the way Cal.ai does once the user approves. Only a business that GET /v2/discover/listings/search returned in webBusinesses to this same user in the last 24 hours can be booked, with its URL exactly as returned; a Discover listing is never booked this way, because it is booked from its bookingUrl. Every check runs before the job starts: the account may book online, has a username, has fewer than the allowed number of jobs running, and its plan covers the booking. The booking agent browses with AI, so a user in an organization that opted out of AI cannot book online. A job that is not started answers created: false with the reason and spends no budget. A started job answers with jobUrl, the job’s page, where the user picks an appointment option, approves the price and terms, or cancels: this API never chooses or approves anything. Every request carries an idempotencyKey the client generates once per booking; send the same key to retry the same request and get the same job back without spending the budget again, even after the business’s allowlist entry expired or its page went down; a deployment that cannot run jobs on Trigger.dev answers created: false. Jobs are limited to 3 an hour per user, shared across all of the user’s API keys and access tokens. If accessed using an OAuth access token, the PROFILE_WRITE scope is required.
curl --request POST \
--url https://api.cal.com/v2/discover/booking-jobs \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"businessUrl": "https://barbers.example/book",
"service": "haircut",
"area": "Capitol Hill, Seattle",
"daysFromToday": 1,
"partOfDay": "afternoon",
"idempotencyKey": "4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10",
"budgetDollars": 60
}
'import requests
url = "https://api.cal.com/v2/discover/booking-jobs"
payload = {
"businessUrl": "https://barbers.example/book",
"service": "haircut",
"area": "Capitol Hill, Seattle",
"daysFromToday": 1,
"partOfDay": "afternoon",
"idempotencyKey": "4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10",
"budgetDollars": 60
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
businessUrl: 'https://barbers.example/book',
service: 'haircut',
area: 'Capitol Hill, Seattle',
daysFromToday: 1,
partOfDay: 'afternoon',
idempotencyKey: '4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10',
budgetDollars: 60
})
};
fetch('https://api.cal.com/v2/discover/booking-jobs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cal.com/v2/discover/booking-jobs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'businessUrl' => 'https://barbers.example/book',
'service' => 'haircut',
'area' => 'Capitol Hill, Seattle',
'daysFromToday' => 1,
'partOfDay' => 'afternoon',
'idempotencyKey' => '4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10',
'budgetDollars' => 60
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cal.com/v2/discover/booking-jobs"
payload := strings.NewReader("{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cal.com/v2/discover/booking-jobs")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cal.com/v2/discover/booking-jobs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"businessUrl\": \"https://barbers.example/book\",\n \"service\": \"haircut\",\n \"area\": \"Capitol Hill, Seattle\",\n \"daysFromToday\": 1,\n \"partOfDay\": \"afternoon\",\n \"idempotencyKey\": \"4b7d1c2e-9f0a-4e1b-8c3d-2a6f5e7d9b10\",\n \"budgetDollars\": 60\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"created": true,
"jobId": "cm9x1booking0job",
"jobUrl": "https://app.cal.com/discover/book/cm9x1booking0job",
"reason": "The assistant cannot book online for this account.",
"signInRequired": true,
"paymentRequired": true,
"planUrl": "https://app.cal.com/settings/discover/preferences"
}
}Headers
Body
Was this page helpful?