curl --request GET \
--url https://api.example.com/api/calendar/v1/public/org/{org_slug} \
--header 'x-kordless-key: <x-kordless-key>'{
"401": {},
"404": {},
"429": {},
"organization": {
"id": "<string>",
"name": "<string>",
"slug": "<string>",
"tagline": "<string>",
"branding": {}
},
"services": [
{
"key": "<string>",
"name": "<string>",
"slug": "<string>",
"description": "<string>",
"category": "<string>",
"duration": 123,
"price": 123,
"currency": "<string>",
"isBookablePublic": true,
"metadata": {}
}
]
}Get organization info and list of bookable services
curl --request GET \
--url https://api.example.com/api/calendar/v1/public/org/{org_slug} \
--header 'x-kordless-key: <x-kordless-key>'{
"401": {},
"404": {},
"429": {},
"organization": {
"id": "<string>",
"name": "<string>",
"slug": "<string>",
"tagline": "<string>",
"branding": {}
},
"services": [
{
"key": "<string>",
"name": "<string>",
"slug": "<string>",
"description": "<string>",
"category": "<string>",
"duration": 123,
"price": 123,
"currency": "<string>",
"isBookablePublic": true,
"metadata": {}
}
]
}GET https://api.kordless.ai/api/calendar/v1/public/org/{org_slug}
acme-salonShow Organization Object
primaryColor: Hex color codelogo: Logo URLcompanyName: Company name for displayfavicon: Favicon URLogImage: Social share image URLShow Service Object
true in this response)curl https://api.kordless.ai/api/calendar/v1/public/org/acme-salon \
-H "x-kordless-key: your_api_key"
{
"organization": {
"id": "org_abc123",
"name": "Acme Salon",
"slug": "acme-salon",
"tagline": "Your neighborhood salon since 2010",
"branding": {
"primaryColor": "#6366f1",
"logo": "https://storage.example.com/acme-logo.png",
"companyName": "Acme Salon & Spa",
"favicon": null,
"ogImage": null
}
},
"services": [
{
"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": {}
},
{
"key": "color",
"name": "Hair Coloring",
"slug": "color",
"description": "Full color treatment with premium products",
"category": "Hair",
"duration": 120,
"price": 150.00,
"currency": "USD",
"isBookablePublic": true,
"metadata": {}
},
{
"key": "manicure",
"name": "Manicure",
"slug": "manicure",
"description": "Classic manicure with polish",
"category": "Nails",
"duration": 30,
"price": 25.00,
"currency": "USD",
"isBookablePublic": true,
"metadata": {}
}
]
}
function ServicePicker({ orgSlug, onSelect }) {
const [organization, setOrganization] = useState(null);
const [services, setServices] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadOrg() {
try {
const { organization, services } = await getOrganization(orgSlug);
setOrganization(organization);
setServices(services);
} catch (error) {
console.error('Failed to load organization:', error);
} finally {
setLoading(false);
}
}
loadOrg();
}, [orgSlug]);
if (loading) return <Spinner />;
// Group services by category
const byCategory = services.reduce((acc, service) => {
const cat = service.category || 'Services';
if (!acc[cat]) acc[cat] = [];
acc[cat].push(service);
return acc;
}, {});
return (
<div className="service-picker">
<header style={{ backgroundColor: organization.branding.primaryColor }}>
{organization.branding.logo && (
<img src={organization.branding.logo} alt={organization.name} />
)}
<h1>{organization.name}</h1>
{organization.tagline && <p>{organization.tagline}</p>}
</header>
{Object.entries(byCategory).map(([category, categoryServices]) => (
<div key={category} className="category">
<h2>{category}</h2>
{categoryServices.map(service => (
<button
key={service.key}
onClick={() => onSelect(service)}
className="service-card"
>
<div className="service-info">
<h3>{service.name}</h3>
<p>{service.description}</p>
<span className="duration">{service.duration} min</span>
</div>
{service.price && (
<span className="price">
${service.price.toFixed(2)}
</span>
)}
</button>
))}
</div>
))}
</div>
);
}
const API_KEY = process.env.KORDLESS_API_KEY;
function BookingWidget({ orgSlug }) {
const [step, setStep] = useState('services'); // services | slots | contact | confirm
const [organization, setOrganization] = useState(null);
const [services, setServices] = useState([]);
const [selectedService, setSelectedService] = useState(null);
const [selectedSlot, setSelectedSlot] = useState(null);
// Step 1: Load organization and services
useEffect(() => {
getOrganization(orgSlug).then(({ organization, services }) => {
setOrganization(organization);
setServices(services);
});
}, [orgSlug]);
// Step 2: Handle service selection
function handleServiceSelect(service) {
setSelectedService(service);
setStep('slots');
}
// Step 3: Handle slot selection
function handleSlotSelect(slot) {
setSelectedSlot(slot);
setStep('contact');
}
// Step 4: Handle booking submission
async function handleBook(contactInfo) {
const { booking } = await createBooking({
org_slug: orgSlug,
service_slug: selectedService.slug,
starts_at: selectedSlot.startsAt,
ends_at: selectedSlot.endsAt,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
contact: contactInfo
});
setStep('confirm');
return booking;
}
return (
<div className="booking-widget">
{step === 'services' && (
<ServicePicker
services={services}
organization={organization}
onSelect={handleServiceSelect}
/>
)}
{step === 'slots' && (
<SlotPicker
orgSlug={orgSlug}
serviceSlug={selectedService.slug}
onSelect={handleSlotSelect}
onBack={() => setStep('services')}
/>
)}
{step === 'contact' && (
<ContactForm
onSubmit={handleBook}
onBack={() => setStep('slots')}
/>
)}
{step === 'confirm' && (
<Confirmation booking={booking} />
)}
</div>
);
}
{
"detail": "Missing x-kordless-key header"
}
{
"detail": "Organization not found"
}