curl --request POST \
--url https://api.usecobalt.com/v1/availability/fetch \
--header 'Content-Type: application/json' \
--header 'access_token: <api-key>' \
--header 'client_id: <api-key>' \
--header 'client_secret: <api-key>' \
--data '
{
"start_date": "2026-03-15",
"end_date": "2026-03-20",
"provider_ids": [
"PROV-1"
],
"buffer": 15,
"visit_type": "FOLLOW-UP",
"calculation_method": "gaps",
"daily_appointment_limit": 8,
"daily_limit_type": "all",
"provider_type": "primary",
"fallback_to_default_hours": false,
"max_slot_duration": 30,
"facility_scoped_scheduling": true,
"location_ids": [
"LOC-1"
],
"cancelled_statuses": [
"canc",
"rs",
"cancsms"
],
"additional_cancelled_statuses": [
"no show",
"bumped"
],
"callback_urls": [
"https://example.com/webhooks/cobalt"
]
}
'import requests
url = "https://api.usecobalt.com/v1/availability/fetch"
payload = {
"start_date": "2026-03-15",
"end_date": "2026-03-20",
"provider_ids": ["PROV-1"],
"buffer": 15,
"visit_type": "FOLLOW-UP",
"calculation_method": "gaps",
"daily_appointment_limit": 8,
"daily_limit_type": "all",
"provider_type": "primary",
"fallback_to_default_hours": False,
"max_slot_duration": 30,
"facility_scoped_scheduling": True,
"location_ids": ["LOC-1"],
"cancelled_statuses": ["canc", "rs", "cancsms"],
"additional_cancelled_statuses": ["no show", "bumped"],
"callback_urls": ["https://example.com/webhooks/cobalt"]
}
headers = {
"client_id": "<api-key>",
"client_secret": "<api-key>",
"access_token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
client_id: '<api-key>',
client_secret: '<api-key>',
access_token: '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
start_date: '2026-03-15',
end_date: '2026-03-20',
provider_ids: ['PROV-1'],
buffer: 15,
visit_type: 'FOLLOW-UP',
calculation_method: 'gaps',
daily_appointment_limit: 8,
daily_limit_type: 'all',
provider_type: 'primary',
fallback_to_default_hours: false,
max_slot_duration: 30,
facility_scoped_scheduling: true,
location_ids: ['LOC-1'],
cancelled_statuses: ['canc', 'rs', 'cancsms'],
additional_cancelled_statuses: ['no show', 'bumped'],
callback_urls: ['https://example.com/webhooks/cobalt']
})
};
fetch('https://api.usecobalt.com/v1/availability/fetch', 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.usecobalt.com/v1/availability/fetch",
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([
'start_date' => '2026-03-15',
'end_date' => '2026-03-20',
'provider_ids' => [
'PROV-1'
],
'buffer' => 15,
'visit_type' => 'FOLLOW-UP',
'calculation_method' => 'gaps',
'daily_appointment_limit' => 8,
'daily_limit_type' => 'all',
'provider_type' => 'primary',
'fallback_to_default_hours' => false,
'max_slot_duration' => 30,
'facility_scoped_scheduling' => true,
'location_ids' => [
'LOC-1'
],
'cancelled_statuses' => [
'canc',
'rs',
'cancsms'
],
'additional_cancelled_statuses' => [
'no show',
'bumped'
],
'callback_urls' => [
'https://example.com/webhooks/cobalt'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"access_token: <api-key>",
"client_id: <api-key>",
"client_secret: <api-key>"
],
]);
$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.usecobalt.com/v1/availability/fetch"
payload := strings.NewReader("{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("client_id", "<api-key>")
req.Header.Add("client_secret", "<api-key>")
req.Header.Add("access_token", "<api-key>")
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.usecobalt.com/v1/availability/fetch")
.header("client_id", "<api-key>")
.header("client_secret", "<api-key>")
.header("access_token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.usecobalt.com/v1/availability/fetch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["client_id"] = '<api-key>'
request["client_secret"] = '<api-key>'
request["access_token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Live-fetch request accepted",
"job_id": 12345
}{
"success": false,
"message": "Bad request — missing or invalid parameters"
}{
"success": false,
"message": "Unauthorized — missing or invalid credentials"
}{
"success": false,
"message": "Internal server error"
}Fetch Availability
Performs a live fetch of provider availability by combining a real-time EMR appointment fetch with availability calculation.
curl --request POST \
--url https://api.usecobalt.com/v1/availability/fetch \
--header 'Content-Type: application/json' \
--header 'access_token: <api-key>' \
--header 'client_id: <api-key>' \
--header 'client_secret: <api-key>' \
--data '
{
"start_date": "2026-03-15",
"end_date": "2026-03-20",
"provider_ids": [
"PROV-1"
],
"buffer": 15,
"visit_type": "FOLLOW-UP",
"calculation_method": "gaps",
"daily_appointment_limit": 8,
"daily_limit_type": "all",
"provider_type": "primary",
"fallback_to_default_hours": false,
"max_slot_duration": 30,
"facility_scoped_scheduling": true,
"location_ids": [
"LOC-1"
],
"cancelled_statuses": [
"canc",
"rs",
"cancsms"
],
"additional_cancelled_statuses": [
"no show",
"bumped"
],
"callback_urls": [
"https://example.com/webhooks/cobalt"
]
}
'import requests
url = "https://api.usecobalt.com/v1/availability/fetch"
payload = {
"start_date": "2026-03-15",
"end_date": "2026-03-20",
"provider_ids": ["PROV-1"],
"buffer": 15,
"visit_type": "FOLLOW-UP",
"calculation_method": "gaps",
"daily_appointment_limit": 8,
"daily_limit_type": "all",
"provider_type": "primary",
"fallback_to_default_hours": False,
"max_slot_duration": 30,
"facility_scoped_scheduling": True,
"location_ids": ["LOC-1"],
"cancelled_statuses": ["canc", "rs", "cancsms"],
"additional_cancelled_statuses": ["no show", "bumped"],
"callback_urls": ["https://example.com/webhooks/cobalt"]
}
headers = {
"client_id": "<api-key>",
"client_secret": "<api-key>",
"access_token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
client_id: '<api-key>',
client_secret: '<api-key>',
access_token: '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
start_date: '2026-03-15',
end_date: '2026-03-20',
provider_ids: ['PROV-1'],
buffer: 15,
visit_type: 'FOLLOW-UP',
calculation_method: 'gaps',
daily_appointment_limit: 8,
daily_limit_type: 'all',
provider_type: 'primary',
fallback_to_default_hours: false,
max_slot_duration: 30,
facility_scoped_scheduling: true,
location_ids: ['LOC-1'],
cancelled_statuses: ['canc', 'rs', 'cancsms'],
additional_cancelled_statuses: ['no show', 'bumped'],
callback_urls: ['https://example.com/webhooks/cobalt']
})
};
fetch('https://api.usecobalt.com/v1/availability/fetch', 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.usecobalt.com/v1/availability/fetch",
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([
'start_date' => '2026-03-15',
'end_date' => '2026-03-20',
'provider_ids' => [
'PROV-1'
],
'buffer' => 15,
'visit_type' => 'FOLLOW-UP',
'calculation_method' => 'gaps',
'daily_appointment_limit' => 8,
'daily_limit_type' => 'all',
'provider_type' => 'primary',
'fallback_to_default_hours' => false,
'max_slot_duration' => 30,
'facility_scoped_scheduling' => true,
'location_ids' => [
'LOC-1'
],
'cancelled_statuses' => [
'canc',
'rs',
'cancsms'
],
'additional_cancelled_statuses' => [
'no show',
'bumped'
],
'callback_urls' => [
'https://example.com/webhooks/cobalt'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"access_token: <api-key>",
"client_id: <api-key>",
"client_secret: <api-key>"
],
]);
$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.usecobalt.com/v1/availability/fetch"
payload := strings.NewReader("{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("client_id", "<api-key>")
req.Header.Add("client_secret", "<api-key>")
req.Header.Add("access_token", "<api-key>")
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.usecobalt.com/v1/availability/fetch")
.header("client_id", "<api-key>")
.header("client_secret", "<api-key>")
.header("access_token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.usecobalt.com/v1/availability/fetch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["client_id"] = '<api-key>'
request["client_secret"] = '<api-key>'
request["access_token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"start_date\": \"2026-03-15\",\n \"end_date\": \"2026-03-20\",\n \"provider_ids\": [\n \"PROV-1\"\n ],\n \"buffer\": 15,\n \"visit_type\": \"FOLLOW-UP\",\n \"calculation_method\": \"gaps\",\n \"daily_appointment_limit\": 8,\n \"daily_limit_type\": \"all\",\n \"provider_type\": \"primary\",\n \"fallback_to_default_hours\": false,\n \"max_slot_duration\": 30,\n \"facility_scoped_scheduling\": true,\n \"location_ids\": [\n \"LOC-1\"\n ],\n \"cancelled_statuses\": [\n \"canc\",\n \"rs\",\n \"cancsms\"\n ],\n \"additional_cancelled_statuses\": [\n \"no show\",\n \"bumped\"\n ],\n \"callback_urls\": [\n \"https://example.com/webhooks/cobalt\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Live-fetch request accepted",
"job_id": 12345
}{
"success": false,
"message": "Bad request — missing or invalid parameters"
}{
"success": false,
"message": "Unauthorized — missing or invalid credentials"
}{
"success": false,
"message": "Internal server error"
}GET /availability instead, which returns availability against
cached appointment data.Request Constraints
provider_ids: required array of EMR provider IDs. Must contain between 1 and 10 entries.- A single provider may span up to a 5-day window.
- Two or more providers must share a single date (
start_datemust equalend_date).
calculation_method, daily_appointment_limit, daily_limit_type, provider_type, fallback_to_default_hours, max_slot_duration, facility_scoped_scheduling, location_ids, cancelled_statuses, additional_cancelled_statuses) are optional and follow the same defaults and validation rules as GET /availability.
Example Request (1 provider, 5 days)
{
"start_date": "2026-07-01",
"end_date": "2026-07-05",
"provider_ids": ["prov_789"],
"calculation_method": "gaps",
"cancelled_statuses": ["canc", "rs", "cancsms"]
}
Example Request (multiple providers, single day)
{
"start_date": "2026-07-01",
"end_date": "2026-07-01",
"provider_ids": ["prov_789", "prov_790", "prov_791"]
}
Webhook Notification
When the fetch completes, we send anavailability.live_fetch_completed event to your registered webhook endpoint with the computed slots inline. The slots themselves are not persisted, but as a side effect the underlying appointments returned by the EMR are written to our appointments cache — a follow-up GET /availability reflects the live state.
{
"id": "evt_1J9X2q2eZvKYlo2Cmnopqr",
"access_token_reference_id": "user_1J9X2q2eZvKYlo2Cstuv",
"job_id": "job_1J9X2q2eZvKYlo2Cmnopqr",
"object": "event",
"created": "2026-07-01T11:00:00Z",
"timestamp": "2026-07-01T11:00:00Z",
"type": "availability.live_fetch_completed",
"action": "cobalt_operation",
"data": {
"success": true,
"provider_ids": ["prov_789"],
"start_date": "2026-07-01",
"end_date": "2026-07-05",
"slot_count": 24,
"slots": [
{
"day_of_week": 3,
"date": "2026-07-01",
"duration_string": "30 minutes",
"range_start": "2026-07-01T09:00:00-07:00",
"range_end": "2026-07-01T09:30:00-07:00",
"provider_id": "prov_789",
"facility_id": "loc_1",
"facility_name": "Main Office"
}
]
}
}
day_of_week (1-7, ISO weekday), date (YYYY-MM-DD), duration_string, range_start, range_end, and provider_id. facility_id and facility_name are present when the source EHR scopes the shift to a specific location. visit_type, available_appointments, and max_visits appear when calculation_method is slots or equal_slots.
Failures and Partial Results
Cobalt retries transient failures automatically. Intermediate failed attempts do not send completion webhooks. A terminal or exhausted failure sendsavailability.live_fetch_completed with success: false, the existing error
message, and reasons: [{ code, description }] when a shared code is known.
An exhausted rate limit may return success: true with partial slots and
an issues[] entry with code: "EHR_RATE_LIMITED". Only provider/days
whose appointments were successfully fetched contribute slots; an unread day is
unknown, not available. Partial results update returned appointments without
deleting cached appointments absent from the incomplete read.
For EHR_RATE_LIMITED, try again later. If it happens frequently, add credential
sets to the account to spread requests across more EHR sessions.Authorizations
Body
Range start (YYYY-MM-DD).
"2026-03-15"
Range end (YYYY-MM-DD). One provider spans up to 5 days; 2–10 providers must share a single day.
"2026-03-20"
EMR provider IDs to fetch availability for (1–10).
["PROV-1"]
Minutes of buffer between slots (0–59).
15
Visit-type code to size slots for. Requires buffer to be set.
"FOLLOW-UP"
Slot calculation method: gaps, slots, or equal_slots.
"gaps"
Max appointments per day (positive integer). Requires daily_limit_type.
8
Visit-type code the daily limit applies to. Requires daily_appointment_limit.
"all"
Which provider slot to match on: primary or secondary.
"primary"
Use default business hours when a provider has no configured hours.
false
Cap on slot length in minutes (5–60, multiples of 5).
30
Scope slots per facility rather than per provider.
true
Filter returned slots to these EMR location IDs.
["LOC-1"]
Appointment statuses to treat as cancelled, freeing the slot. When provided, this overrides the default cancelled/rescheduled detection. The default detection treats "c", "cancelled", "canc", "rescheduled", "r/s" and "deleted" as cancelled (PTPracticePro uses "3" in place of "c"); for eClinicalWorks it also treats any status containing "canc", "r/s" or "resched" as cancelled. Statuses are matched case-insensitively. To keep the default detection and add your own statuses, use additional_cancelled_statuses instead.
["canc", "rs", "cancsms"]
Appointment statuses to treat as cancelled in addition to the default cancelled/rescheduled detection (or to cancelled_statuses, when provided); statuses are matched case-insensitively. Use this to add custom status codes without losing the defaults.
["no show", "bumped"]
URLs to receive the results webhook, in addition to the account webhook.
["https://example.com/webhooks/cobalt"]