curl --request GET \
--url https://api.example.com/api/calendar/v1/public/org/{org_slug}/service/{service_slug} \
--header 'x-kordless-key: <x-kordless-key>'{
"401": {},
"404": {},
"429": {},
"organization": {
"id": "<string>",
"name": "<string>",
"slug": "<string>",
"branding": {}
},
"service": {
"key": "<string>",
"name": "<string>",
"slug": "<string>",
"description": "<string>",
"category": "<string>",
"duration": 123,
"price": 123,
"currency": "<string>",
"isBookablePublic": true,
"metadata": {}
},
"calendarSettings": {
"duration_minutes": 123,
"slot_interval_minutes": 123,
"buffer_before": 123,
"buffer_after": 123,
"booking_window_days": 123,
"timezone": "<string>",
"location": {},
"max_parallel_bookings": 123,
"cancellation_policy": {}
}
}Get service details and calendar settings
curl --request GET \
--url https://api.example.com/api/calendar/v1/public/org/{org_slug}/service/{service_slug} \
--header 'x-kordless-key: <x-kordless-key>'{
"401": {},
"404": {},
"429": {},
"organization": {
"id": "<string>",
"name": "<string>",
"slug": "<string>",
"branding": {}
},
"service": {
"key": "<string>",
"name": "<string>",
"slug": "<string>",
"description": "<string>",
"category": "<string>",
"duration": 123,
"price": 123,
"currency": "<string>",
"isBookablePublic": true,
"metadata": {}
},
"calendarSettings": {
"duration_minutes": 123,
"slot_interval_minutes": 123,
"buffer_before": 123,
"buffer_after": 123,
"booking_window_days": 123,
"timezone": "<string>",
"location": {},
"max_parallel_bookings": 123,
"cancellation_policy": {}
}
}GET https://api.kordless.ai/api/calendar/v1/public/org/{org_slug}/service/{service_slug}
acme-salonhaircutShow Service Object
Show Calendar Settings
type: onsite, phone, meet_url, or customvalue: Address, phone number, or meeting linknoticeHours: Required notice for cancellationpolicyText: Policy descriptioncurl https://api.kordless.ai/api/calendar/v1/public/org/acme-salon/service/haircut \
-H "x-kordless-key: your_api_key"
{
"organization": {
"id": "org_abc123",
"name": "Acme Salon",
"slug": "acme-salon",
"branding": {
"primaryColor": "#6366f1",
"logo": "https://storage.example.com/acme-logo.png",
"companyName": "Acme Salon & Spa"
}
},
"service": {
"key": "haircut",
"name": "Haircut",
"slug": "haircut",
"description": "Professional haircut with wash and style",
"category": "Hair",
"duration": 60,
"price": 45.00,
"currency": "USD",
"isBookablePublic": true,
"metadata": {}
},
"calendarSettings": {
"duration_minutes": 60,
"slot_interval_minutes": 30,
"buffer_before": 0,
"buffer_after": 15,
"booking_window_days": 30,
"timezone": "America/New_York",
"location": {
"type": "onsite",
"value": "123 Main St, Suite 100, New York, NY 10001"
},
"max_parallel_bookings": 1,
"cancellation_policy": {
"noticeHours": 24,
"policyText": "Please cancel at least 24 hours in advance to avoid a cancellation fee."
}
}
}
function ServiceDetail({ orgSlug, serviceSlug, onBook }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
getService(orgSlug, serviceSlug)
.then(setData)
.finally(() => setLoading(false));
}, [orgSlug, serviceSlug]);
if (loading) return <Spinner />;
if (!data) return <NotFound />;
const { organization, service, calendarSettings } = data;
return (
<div className="service-detail">
<header>
<h1>{service.name}</h1>
<p className="org-name">{organization.name}</p>
</header>
<section className="details">
<p className="description">{service.description}</p>
<div className="info-grid">
<div className="info-item">
<span className="label">Duration</span>
<span className="value">{service.duration} minutes</span>
</div>
{service.price && (
<div className="info-item">
<span className="label">Price</span>
<span className="value">
${service.price.toFixed(2)} {service.currency}
</span>
</div>
)}
{calendarSettings.location && (
<div className="info-item">
<span className="label">Location</span>
<span className="value">
{calendarSettings.location.type === 'onsite' && '📍 '}
{calendarSettings.location.type === 'phone' && '📞 '}
{calendarSettings.location.type === 'meet_url' && '💻 '}
{calendarSettings.location.value}
</span>
</div>
)}
</div>
{calendarSettings.cancellation_policy?.policyText && (
<div className="cancellation-policy">
<h3>Cancellation Policy</h3>
<p>{calendarSettings.cancellation_policy.policyText}</p>
</div>
)}
</section>
<button onClick={onBook} className="book-button">
Book {service.name}
</button>
</div>
);
}
const { calendarSettings } = await getService(orgSlug, serviceSlug);
// Calculate booking window
const maxBookingDate = new Date();
maxBookingDate.setDate(maxBookingDate.getDate() + calendarSettings.booking_window_days);
// Show location appropriately
function formatLocation(location) {
switch (location.type) {
case 'onsite':
return `In-person at ${location.value}`;
case 'phone':
return `Phone call to ${location.value}`;
case 'meet_url':
return `Video call: ${location.value}`;
default:
return location.value;
}
}
// Display cancellation policy
if (calendarSettings.cancellation_policy) {
const { noticeHours, policyText } = calendarSettings.cancellation_policy;
showPolicy({
title: `${noticeHours}-hour cancellation notice required`,
details: policyText
});
}
{
"detail": "Missing x-kordless-key header"
}
{
"detail": "Service not found"
}