Demo availability and booking guide

Read available times with a simple API, then book a founder-led demo with the attendee’s approval.

This public interface schedules a 30-minute Google Meet demonstration. It does not access customer data, the Data Hippo platform, or an administrative calendar.

Read this guide as Markdown → · OpenAPI specification →

When should an agent use this interface?

Use the platform guide and product reference to answer questions about Data Hippo. Use GET /demo/slots only when someone wants to see meeting times. Use POST /demo/book only when the attendee has approved one specific time and their contact details. Reading the website is not permission to book.

This is a public demo scheduler, not a developer API for querying healthcare data or controlling a customer deployment. If the request needs those capabilities, contact the team rather than inventing an endpoint.

Read availability, then book an approved time

These examples use the production availability endpoint. Discover the current endpoint from servers[0].url in the OpenAPI specification. A locally served specification points to development infrastructure. Both environments use the real calendar; development is not a booking dry run.

Run one example in your terminal. Each reads the next seven days from the current UTC instant, using America/New_York as the display timezone. Change that value to the attendee’s IANA timezone. No credential, package install, or forged browser headers are needed. By default, these examples only read availability; they never choose or book the first slot.

After the attendee approves a returned time and their details, create booking.json using the template below. Replace every placeholder. Set attendee_confirmed to true only after approval. Optional fields are organization and comments. Never include health data or secrets.

{
  "start": "REPLACE_WITH_APPROVED_UTC_ISO_TIME",
  "timezone": "America/New_York",
  "name": "REPLACE_WITH_APPROVED_NAME",
  "email": "REPLACE_WITH_APPROVED_EMAIL",
  "attendee_confirmed": false
}

Create and retain one IDEMPOTENCY_KEY for this exact booking: current Unix milliseconds, a period, and a UUIDv4. For example, JavaScript generates it with Date.now() + '.' + require('node:crypto').randomUUID(); Python uses str(int(time.time() * 1000)) + '.' + str(uuid.uuid4()); PowerShell uses [string][DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() + '.' + [guid]::NewGuid().ToString(). Bash can use the Node or Python command. Save the resulting value before submission; do not regenerate it for a retry.

Set the environment variables IDEMPOTENCY_KEY to that value and BOOKING_CONFIRMED=yes only when ready to submit the approved request. Rerun the same example. It reads availability again, then sends your chosen payload once. Keep responses private: they may contain attendee cancellation and rescheduling links.

Save JavaScript as availability.cjs and run node availability.cjs. Save Python as availability.py and run python3 availability.py. Paste the Bash or PowerShell example into its respective shell.

JavaScript · Node.js

// Node.js: built-in https; no packages or browser headers.
const https = require('node:https');
const url = new URL('https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/slots');
url.search = new URLSearchParams({
  after: new Date().toISOString(), days: '7', timezone: 'America/New_York'
});
const request = https.get(url, { timeout: 15000 }, response => {
  let body = '';
  response.setEncoding('utf8');
  response.on('data', chunk => { body += chunk; });
  response.on('end', () => {
    if (response.statusCode !== 200) {
      console.error('HTTP', response.statusCode, body);
      if (response.statusCode === 429)
        console.error('Retry after:', response.headers['retry-after'] || '60', 'seconds');
      process.exitCode = 1;
      return;
    }
    console.log(body);
    // Optional second step, only after the attendee approves booking.json.
    if (process.env.BOOKING_CONFIRMED !== 'yes') return;
    const key = process.env.IDEMPOTENCY_KEY;
    if (!key) throw new Error('Set and retain IDEMPOTENCY_KEY first');
    const payload = require('node:fs').readFileSync('booking.json', 'utf8');
    if (JSON.parse(payload).attendee_confirmed !== true) throw new Error('Approval required');
    const booking = https.request(new URL('/v1/demo/book', url), {
      method: 'POST', timeout: 20000,
      headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key }
    }, result => {
      let receipt = '';
      result.setEncoding('utf8');
      result.on('data', chunk => { receipt += chunk; });
      result.on('end', () => {
        console.log('Booking HTTP', result.statusCode, receipt);
        if (result.statusCode !== 200) process.exitCode = 1;
      });
    });
    booking.on('timeout', () => booking.destroy(new Error('Booking status unknown; retain the same key and payload')));
    booking.on('error', error => { console.error(error.message); process.exitCode = 1; });
    booking.end(payload);
  });
});
request.on('timeout', () => request.destroy(new Error('Availability request timed out')));
request.on('error', error => { console.error(error.message); process.exitCode = 1; });

Python · standard library

from datetime import datetime, timezone
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
import sys
import os
import json

params = urlencode({
    "after": datetime.now(timezone.utc).isoformat(),
    "days": 7, "timezone": "America/New_York"
})
url = "https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/slots?" + params
try:
    request = Request(url, headers={"User-Agent": "DataHippoAvailabilityExample/1.0"})
    with urlopen(request, timeout=15) as response:
        print(response.read().decode("utf-8"))
    # Optional second step, only after the attendee approves booking.json.
    if os.environ.get("BOOKING_CONFIRMED") == "yes":
        key = os.environ["IDEMPOTENCY_KEY"]  # Retain this key for this exact request.
        with open("booking.json", encoding="utf-8") as source:
            payload = json.load(source)
        if payload.get("attendee_confirmed") is not True:
            raise ValueError("Approval required")
        booking = Request("https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/book",
            data=json.dumps(payload).encode(), method="POST",
            headers={"Content-Type": "application/json", "Idempotency-Key": key,
                     "User-Agent": "DataHippoAvailabilityExample/1.0"})
        with urlopen(booking, timeout=20) as response:
            print(response.read().decode("utf-8"))
except HTTPError as error:
    print("HTTP", error.code, error.read().decode("utf-8"), file=sys.stderr)
    if error.code == 429:
        print("Retry after:", error.headers.get("Retry-After", "60"), "seconds", file=sys.stderr)
    sys.exit(1)
except (URLError, TimeoutError, ValueError, KeyError, OSError) as error:
    print(str(error), file=sys.stderr)
    sys.exit(1)

Bash · curl and date

# macOS or Linux. curl prints response headers (including Retry-After) and JSON.
# --fail-with-body requires curl 7.76 or later; no jq or packages required.
curl --silent --show-error --fail-with-body --include --max-time 15 \
  --get 'https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/slots' \
  --data-urlencode "after=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
  --data-urlencode 'days=7' \
  --data-urlencode 'timezone=America/New_York' || exit $?

# Optional second step. Review booking.json with the attendee before setting yes.
if [ "${BOOKING_CONFIRMED:-}" = yes ]; then
  : "${IDEMPOTENCY_KEY:?Set and retain IDEMPOTENCY_KEY first}"
  curl --silent --show-error --fail-with-body --include --max-time 20 \
    'https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/book' \
    -H 'Content-Type: application/json' -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    --data-binary @booking.json
fi

PowerShell · native cmdlet

# PowerShell 5.1 or 7+, no modules required.
$after = [Uri]::EscapeDataString([DateTimeOffset]::UtcNow.ToString("o"))
$zone = [Uri]::EscapeDataString("America/New_York")
$url = "https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/slots?after=$after&days=7&timezone=$zone"
try {
    $result = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 15 -ErrorAction Stop
    $result | ConvertTo-Json -Depth 5
    # Optional second step, only after the attendee approves booking.json.
    if ($env:BOOKING_CONFIRMED -eq 'yes') {
        if (-not $env:IDEMPOTENCY_KEY) { throw 'Set and retain IDEMPOTENCY_KEY first' }
        $payload = Get-Content -Raw booking.json
        if (($payload | ConvertFrom-Json).attendee_confirmed -ne $true) { throw 'Approval required' }
        $booking = @{
            Uri = 'https://dh-booking-prod.fritz-9f9.workers.dev/v1/demo/book'
            Method = 'Post'; ContentType = 'application/json'; Body = $payload
            Headers = @{ 'Idempotency-Key' = $env:IDEMPOTENCY_KEY }; TimeoutSec = 20; ErrorAction = 'Stop'
        }
        Invoke-RestMethod @booking | ConvertTo-Json -Depth 5
    }
} catch {
    $response = $_.Exception.Response
    if ($response) {
        Write-Error ("HTTP " + [int]$response.StatusCode + ": " + $_.ErrorDetails.Message)
        if ([int]$response.StatusCode -eq 429) {
            Write-Host "Wait at least 60 seconds before requesting availability again."
        }
    } else {
        Write-Error $_.Exception.Message
    }
    exit 1
}

The JSON response contains slots with UTC iso start times, and earliest/latest bounds in Unix milliseconds. An empty list means no availability in that interval. A read does not reserve a time. Calendly owns minimum notice, working hours, and calendar conflicts; do not infer a fixed notice period. Only complete months within the next 90 days are offered.

Availability requests time out after 15 seconds; booking requests after 20 seconds. For HTTP 429, wait the number of seconds in Retry-After before another read; if absent, wait at least 60 seconds. For other failures, show the error instead of presenting stale slots as available. Do not repeatedly poll or automatically retry booking submissions.

Book with an agent or in a browser

  1. Read availability, then present a few choices in the attendee’s timezone. Recheck availability before final selection; times can change.
  2. Get the attendee’s explicit approval of the date, time, timezone, name, and email before submitting. Never submit health data or secrets.
  3. POST the approved request to /demo/book with attendee_confirmed: true and the retained Idempotency-Key, or use the browser calendar at https://datahippo.ai/contact/. Both create a real invitation.
  4. HTTP 200 confirms the booking. The same key and unchanged payload replay the original result, not another invitation. After a timeout, retain the exact key and payload; never generate a replacement key automatically.
  5. Keys expire 24 hours after their timestamp. HTTP 409 with idempotency_conflict means the payload changed; booking_unconfirmed means the outcome is pending or uncertain; idempotency_expired means the key expired. For an uncertain or expired outcome, check the invitation or contact us before any new attempt.
  6. Keep the invitee’s cancellation and rescheduling links private. Share them only with the attendee; they are not administrative links.

Submission creates a real invitation. HTTP 429 requires waiting for Retry-After; HTTP 503 means scheduling is unavailable. Do not automatically retry submissions. If booking cannot be confirmed, give the attendee https://datahippo.ai/contact/ and the proposed times instead. Contact information →

Errors, rate limits, and API changes

API errors follow the ApiError schema: an error message, machine-readable code, and safe next action in resolution. Handle HTTP status first, then a recognized code; do not depend on exact message wording. Unknown errors should be shown safely to the requester, without treating the booking as confirmed.

  • 400: Check required fields, explicit approval, and the timestamped key. Unknown booking fields are rejected.
  • 403: The request origin is not allowed. Server clients omit Origin, Sec-Fetch-Site, and Sec-Fetch-Dest; native fetch Sec-Fetch-Mode is accepted. Do not spoof browser headers.
  • 404: Check the method and route against the OpenAPI specification.
  • 409: A slot or idempotency conflict needs attention. Pending or uncertain bookings must be checked before another attempt.
  • 413 / 415: Send JSON with Content-Type: application/json, within the 4,096-byte request limit.
  • 429: Respect Retry-After in seconds; the current limit window is 60 seconds. Avoid polling loops. Keep the same key and payload if a booking is retried.
  • 503: Scheduling is unavailable. Do not interpret this as proof that an earlier booking did not happen.

The API publishes RateLimit-Limit and RateLimit-Policy: 30 availability reads or 3 booking attempts per 60-second window, per client IP and Cloudflare location. Enforcement is distributed, not an exact global counter. No remaining-quota or reset timestamp is claimed; use Retry-After on HTTP 429.

The live OpenAPI document is the current contract. Its server URL includes /v1; append the documented operation path. Legacy unversioned routes remain supported. Changes within v1 remain backward-compatible and additive; a breaking change requires a new major-version URL. Handle unfamiliar optional fields and error codes safely. info.version identifies the document revision. There is no fixed deprecation deadline or API support SLA; contact us about compatibility needs.