Build an integration
From discovery to slot in 20 lines
Four sample programs that pull a tenant’s services + look up the earliest available slot. JavaScript / Python / Bash / Go. Working code, not pseudocode: paste + replace the tenant slug + run.
The pattern
Every integration follows the same three-step pattern:
- 01Pull
/api/mcp/<tenant>to discover services + the canonical booking URL. - 02Pick a service by id (or just the first public one).
- 03Pull
/api/mcp/<tenant>/availability/next?service=<id>to get the earliest slot + the deep-link booking URL.
Direct the user to the booking URL with the slot preselected: they confirm + complete the booking through the standard web flow.
Samples
Four languages
JavaScript (Node 18+, Bun, Deno)
goldenhour-availability.js// Look up the earliest available slot for a service on goldenhour.
// Works with Node 18+, Bun, or Deno — native fetch + JSON.
const BASE = "https://goldenhourhq.com";
const TENANT = process.env.GOLDENHOUR_TENANT || "sobetan";
const SERVICE_ID = process.env.GOLDENHOUR_SERVICE_ID;
async function findNextSlot() {
// Discovery: pull the per-tenant manifest first.
const tenantUrl = `${BASE}/api/mcp/${encodeURIComponent(TENANT)}`;
const tenant = await fetch(tenantUrl).then((r) => {
if (!r.ok) throw new Error(`tenant fetch ${r.status}`);
return r.json();
});
console.log(`Booking with ${tenant.business.name} (${tenant.business.mode})`);
console.log(`Booking URL: ${tenant.business.booking_url}`);
// Pick a service (first public one if no env override).
const service = SERVICE_ID
? tenant.services.find((s) => s.id === SERVICE_ID)
: tenant.services[0];
if (!service) throw new Error("no public services found");
console.log(`Service: ${service.name} (${service.duration_minutes}min)`);
// Compact next-slot lookup.
const nextUrl = `${BASE}/api/mcp/${encodeURIComponent(TENANT)}` +
`/availability/next?service=${encodeURIComponent(service.id)}`;
const next = await fetch(nextUrl).then((r) => r.json());
if (!next.next_slot) {
console.log("No slots available in the next 30 days.");
return;
}
console.log(`Next slot: ${next.next_slot} (${next.timezone})`);
console.log(`Booking deep link: ${next.booking_url}`);
}
findNextSlot().catch((err) => {
console.error(err);
process.exit(1);
});Python (3.10+)
goldenhour_availability.py# Look up the earliest available slot. Python 3.10+, stdlib only.
import os
import sys
import urllib.parse
import urllib.request
import json
BASE = "https://goldenhourhq.com"
TENANT = os.environ.get("GOLDENHOUR_TENANT", "sobetan")
SERVICE_ID = os.environ.get("GOLDENHOUR_SERVICE_ID")
def fetch_json(url: str) -> dict:
with urllib.request.urlopen(url, timeout=10) as resp:
return json.loads(resp.read())
def main() -> None:
tenant_url = f"{BASE}/api/mcp/{urllib.parse.quote(TENANT)}"
tenant = fetch_json(tenant_url)
print(f"Booking with {tenant['business']['name']} "
f"({tenant['business']['mode']})")
print(f"Booking URL: {tenant['business']['booking_url']}")
services = tenant["services"]
if SERVICE_ID:
service = next((s for s in services if s["id"] == SERVICE_ID), None)
else:
service = services[0] if services else None
if not service:
sys.exit("no public services found")
print(f"Service: {service['name']} ({service['duration_minutes']}min)")
next_url = (
f"{BASE}/api/mcp/{urllib.parse.quote(TENANT)}/availability/next"
f"?service={urllib.parse.quote(service['id'])}"
)
nxt = fetch_json(next_url)
if not nxt.get("next_slot"):
print("No slots available in the next 30 days.")
return
print(f"Next slot: {nxt['next_slot']} ({nxt['timezone']})")
print(f"Booking deep link: {nxt['booking_url']}")
if __name__ == "__main__":
main()Bash (curl + jq)
goldenhour-availability.sh#!/usr/bin/env bash
# Earliest-slot lookup. Needs curl + jq.
set -euo pipefail
BASE="${BASE:-https://goldenhourhq.com}"
TENANT="${GOLDENHOUR_TENANT:-sobetan}"
# Pull the tenant summary.
tenant_json=$(curl -fsSL "${BASE}/api/mcp/${TENANT}")
echo "Booking with $(echo "$tenant_json" | jq -r '.business.name')"
echo "Booking URL: $(echo "$tenant_json" | jq -r '.business.booking_url')"
# Pick the first public service (or override with GOLDENHOUR_SERVICE_ID).
service_id="${GOLDENHOUR_SERVICE_ID:-$(echo "$tenant_json" | jq -r '.services[0].id')}"
[ -z "${service_id}" ] && { echo "no service id" >&2; exit 1; }
# Compact next-slot lookup.
next_json=$(curl -fsSL \
"${BASE}/api/mcp/${TENANT}/availability/next?service=${service_id}")
next_slot=$(echo "$next_json" | jq -r '.next_slot // empty')
if [ -z "${next_slot}" ]; then
echo "No slots available in the next 30 days."
exit 0
fi
echo "Next slot: ${next_slot} ($(echo "$next_json" | jq -r '.timezone'))"
echo "Booking deep link: $(echo "$next_json" | jq -r '.booking_url')"Go (1.21+)
main.gopackage main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
const baseURL = "https://goldenhourhq.com"
type tenantResp struct {
Business struct {
Name string `json:"name"`
Mode string `json:"mode"`
BookingURL string `json:"booking_url"`
} `json:"business"`
Services []struct {
ID string `json:"id"`
Name string `json:"name"`
DurationMinutes int `json:"duration_minutes"`
} `json:"services"`
}
type nextResp struct {
NextSlot *string `json:"next_slot"`
Timezone string `json:"timezone"`
BookingURL string `json:"booking_url"`
}
func fetchJSON(target string, out interface{}) error {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(target)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("status %d", resp.StatusCode)
}
return json.NewDecoder(resp.Body).Decode(out)
}
func main() {
tenant := os.Getenv("GOLDENHOUR_TENANT")
if tenant == "" {
tenant = "sobetan"
}
var t tenantResp
if err := fetchJSON(baseURL+"/api/mcp/"+url.PathEscape(tenant), &t); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("Booking with %s (%s)\n", t.Business.Name, t.Business.Mode)
fmt.Printf("Booking URL: %s\n", t.Business.BookingURL)
if len(t.Services) == 0 {
fmt.Fprintln(os.Stderr, "no public services")
os.Exit(1)
}
svc := t.Services[0]
var n nextResp
if err := fetchJSON(
baseURL+"/api/mcp/"+url.PathEscape(tenant)+
"/availability/next?service="+url.QueryEscape(svc.ID), &n,
); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if n.NextSlot == nil {
fmt.Println("No slots available.")
return
}
fmt.Printf("Next slot: %s (%s)\n", *n.NextSlot, n.Timezone)
fmt.Printf("Booking deep link: %s\n", n.BookingURL)
}Going deeper
JSON Schema
Strict response validation: see /api/mcp/spec.json.
OpenAPI 3.1
Full request shape: /api/openapi.json.
Curl quick-start