Skip to main content
GET
/
api
/
calendar
/
v1
/
public
/
org
/
{org_slug}
Get Organization
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": {}
    }
  ]
}

Endpoint

GET https://api.kordless.ai/api/calendar/v1/public/org/{org_slug}
Retrieves organization information and all publicly bookable services. This is typically the first call in a booking flow to display service options to customers.

Authentication

x-kordless-key
string
required
Your API key

Path Parameters

org_slug
string
required
Your organization slug (from your booking page URL)Example: acme-salon

Response

organization
object
Organization information
services
array
List of publicly bookable services

Examples

curl https://api.kordless.ai/api/calendar/v1/public/org/acme-salon \
  -H "x-kordless-key: your_api_key"

Response Example

{
  "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": {}
    }
  ]
}

Building a Service Picker

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>
  );
}

Complete Booking Widget Flow

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>
  );
}

Errors

401
Unauthorized
Invalid or missing API key
{
  "detail": "Missing x-kordless-key header"
}
404
Not Found
Organization not found
{
  "detail": "Organization not found"
}
429
Too Many Requests
Rate limit exceeded