Find Mutual Availability
curl --request POST \
--url https://api.example.com/v1/availability \
--header 'Content-Type: application/json' \
--data '
{
"attendees": [
"<string>"
],
"duration_minutes": 123,
"date_range": {
"start_date": "<string>",
"end_date": "<string>"
},
"context": "<string>"
}
'import requests
url = "https://api.example.com/v1/availability"
payload = {
"attendees": ["<string>"],
"duration_minutes": 123,
"date_range": {
"start_date": "<string>",
"end_date": "<string>"
},
"context": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
attendees: ['<string>'],
duration_minutes: 123,
date_range: {start_date: '<string>', end_date: '<string>'},
context: '<string>'
})
};
fetch('https://api.example.com/v1/availability', 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.example.com/v1/availability",
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([
'attendees' => [
'<string>'
],
'duration_minutes' => 123,
'date_range' => [
'start_date' => '<string>',
'end_date' => '<string>'
],
'context' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"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.example.com/v1/availability"
payload := strings.NewReader("{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.example.com/v1/availability")
.header("Content-Type", "application/json")
.body("{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/availability")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"slots": [
{
"start_time": "<string>",
"end_time": "<string>",
"score": 123,
"timezone": "<string>",
"reason": "<string>"
}
],
"total_found": 123,
"search_window": {
"start": "<string>",
"end": "<string>",
"days": 123
}
}Scheduling API
Find Mutual Availability
Find time slots where all attendees are available
POST
/
v1
/
availability
Find Mutual Availability
curl --request POST \
--url https://api.example.com/v1/availability \
--header 'Content-Type: application/json' \
--data '
{
"attendees": [
"<string>"
],
"duration_minutes": 123,
"date_range": {
"start_date": "<string>",
"end_date": "<string>"
},
"context": "<string>"
}
'import requests
url = "https://api.example.com/v1/availability"
payload = {
"attendees": ["<string>"],
"duration_minutes": 123,
"date_range": {
"start_date": "<string>",
"end_date": "<string>"
},
"context": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
attendees: ['<string>'],
duration_minutes: 123,
date_range: {start_date: '<string>', end_date: '<string>'},
context: '<string>'
})
};
fetch('https://api.example.com/v1/availability', 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.example.com/v1/availability",
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([
'attendees' => [
'<string>'
],
'duration_minutes' => 123,
'date_range' => [
'start_date' => '<string>',
'end_date' => '<string>'
],
'context' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"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.example.com/v1/availability"
payload := strings.NewReader("{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.example.com/v1/availability")
.header("Content-Type", "application/json")
.body("{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/availability")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"attendees\": [\n \"<string>\"\n ],\n \"duration_minutes\": 123,\n \"date_range\": {\n \"start_date\": \"<string>\",\n \"end_date\": \"<string>\"\n },\n \"context\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"slots": [
{
"start_time": "<string>",
"end_time": "<string>",
"score": 123,
"timezone": "<string>",
"reason": "<string>"
}
],
"total_found": 123,
"search_window": {
"start": "<string>",
"end": "<string>",
"days": 123
}
}Endpoint
POST https://api.syncline.run/v1/availability
Request Body
string[]
required
Array of email addresses. Must be exactly 2 for MVP.
["alice@example.com", "bob@example.com"]
integer
default:30
Meeting duration in minutes. Min: 15, Max: 120.
object
string
Optional meeting context for better title generation.Example:
"Introduction call after LinkedIn connection"Response
TimeSlot[]
Array of 5 time slots, ranked by quality (best first).
integer
Total number of slots found (always 5 or less)
object
Example Request
curl -X POST https://api.syncline.run/v1/availability \
-H "X-API-Key: sk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"attendees": ["alice@example.com", "bob@example.com"],
"duration_minutes": 30,
"context": "Introductory call"
}'
import requests
response = requests.post(
'https://api.syncline.run/v1/availability',
headers={
'X-API-Key': 'sk_live_abc123...',
'Content-Type': 'application/json'
},
json={
'attendees': ['alice@example.com', 'bob@example.com'],
'duration_minutes': 30,
'context': 'Introductory call'
}
)
data = response.json()
best_slot = data['slots'][0] # First slot is best
const response = await fetch('https://api.syncline.run/v1/availability', {
method: 'POST',
headers: {
'X-API-Key': 'sk_live_abc123...',
'Content-Type': 'application/json'
},
body: JSON.stringify({
attendees: ['alice@example.com', 'bob@example.com'],
duration_minutes: 30,
context: 'Introductory call'
})
});
const data = await response.json();
const bestSlot = data.slots[0]; // First slot is best
Example Response
{
"slots": [
{
"start_time": "2025-11-20T10:00:00-08:00",
"end_time": "2025-11-20T10:30:00-08:00",
"score": 0.95,
"timezone": "America/Los_Angeles",
"reason": "Mid-morning, optimal timezone for both attendees"
},
{
"start_time": "2025-11-21T10:30:00-08:00",
"end_time": "2025-11-21T11:00:00-08:00",
"score": 0.93,
"timezone": "America/Los_Angeles",
"reason": "Mid-morning, excellent availability"
},
{
"start_time": "2025-11-20T14:00:00-08:00",
"end_time": "2025-11-20T14:30:00-08:00",
"score": 0.82,
"timezone": "America/Los_Angeles",
"reason": "Early afternoon, good match"
},
{
"start_time": "2025-11-22T11:00:00-08:00",
"end_time": "2025-11-22T11:30:00-08:00",
"score": 0.80,
"timezone": "America/Los_Angeles",
"reason": "Late morning, acceptable for both"
},
{
"start_time": "2025-11-20T16:00:00-08:00",
"end_time": "2025-11-20T16:30:00-08:00",
"score": 0.75,
"timezone": "America/Los_Angeles",
"reason": "Late afternoon, available but not ideal"
}
],
"total_found": 5,
"search_window": {
"start": "2025-11-15",
"end": "2025-11-29",
"days": 14
}
}
Error Responses
Invalid Attendees
Invalid Attendees
{
"error": {
"code": "invalid_attendees",
"message": "Attendee list must contain exactly 2 email addresses",
"field": "attendees"
}
}
400 Bad RequestUser Not Found
User Not Found
{
"error": {
"code": "user_not_found",
"message": "User alice@example.com has not connected their calendar",
"field": "attendees",
"email": "alice@example.com"
}
}
404 Not FoundSolution: User must complete OAuth flow at syncline.runNo Availability Found
No Availability Found
{
"slots": [],
"total_found": 0,
"search_window": {
"start": "2025-11-15",
"end": "2025-11-29",
"days": 14
},
"message": "No mutual availability found in 14-day window"
}
200 OKNote: This is not an error. Try expanding the date range or adjusting preferences.How Slots Are Ranked
Syncline uses a smart ranking algorithm with 5 factors:1. Timezone Fairness
1. Timezone Fairness
Avoids times outside 8am-6pm for either person.Score impact: 0.5x penalty for awkward hours
2. Mid-Morning Bonus
2. Mid-Morning Bonus
10am is the ideal meeting time for most professionals.Score impact: 1.2x boost for 10am slots
3. Friday Afternoon Penalty
3. Friday Afternoon Penalty
People protect Friday afternoons for focus work.Score impact: 0.7x penalty after 2pm on Fridays
4. Lunch Hour Penalty
4. Lunch Hour Penalty
Noon-1pm is typically reserved for lunch.Score impact: 0.8x penalty for 12pm-1pm
5. Recency Bonus
5. Recency Bonus
Sooner is better—momentum matters for intros.Score impact: 1.1x boost for slots within 3 days
Best Practices
Always use the first slot. It’s ranked highest for a reason. Only offer alternatives if user explicitly requests them.
Cache results for 5 minutes. Availability doesn’t change that fast. Reduces API calls.
Show timezone to user. Even though Syncline handles conversion, transparency builds trust.
Next Steps
Schedule Meeting
Book one of the available slots
Smart Ranking
Deep dive into the ranking algorithm