Embedding in a web page

The API answers browser requests from any origin (Access-Control-Allow-Origin: *), so a studio's website can show live schedules and prices with a few lines of JavaScript and no server of its own.

Use a publishable key

Anything in a web page is public, including the key. Use a publishable key (hg_pk_…), which exists for exactly this. Never put a secret key (hg_sk_…) in a page, an app bundle or a public repository.

The pattern

<section>
  <h2>Class schedule</h2>
  <div id="schedule">Loading…</div>
</section>
<script>
  const KEY = 'hg_pk_…'
  fetch('https://go.hotglue.app/api/v1/classes?limit=50', { headers: { 'X-API-Key': KEY } })
    .then(r => r.ok ? r.json() : Promise.reject(r.status))
    .then(({ data }) => {
      const el = document.getElementById('schedule')
      if (data.length === 0) { el.innerHTML = '<p>New classes coming soon.</p>'; return }
      el.innerHTML = data.map(c => `
        <article>
          <h3>${esc(c.name)}</h3>
          <p>${esc(c.schedule_description ?? '')}</p>
          <p>${esc(c.price_display)}</p>
          <a href="${c.links.checkout}">Enroll</a>
        </article>`).join('')
    })
    .catch(() => { document.getElementById('schedule').innerHTML = '<p>Schedule temporarily unavailable.</p>' })

  function esc(s) { return String(s).replace(/[&<>"']/g, ch => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]) }
</script>

Three states, always: loading, empty, and error. A studio with no classes yet should see a friendly line, not a blank box; a network blip should not leave "Loading…" on the page forever.

Rendering tips

  • Escape text you insert into HTML (names, descriptions). The studio typed them; treat them as untrusted. The links.* URLs are built by HotGlue, not typed by the studio, and are safe to put straight into an href.
  • Use the ready-made fields. schedule_description and price_display are formatted the way the studio shows them. links.* are complete URLs: send visitors there instead of building HotGlue URLs by hand.
  • Null is normal. category, location, description, featured_image_url and others are null when the studio hasn't set them, and null is the only blank you'll see (never ""). Use c.location?.name.
  • Hide finished classes. A class stays listed until the studio archives it, even after its end_date. Pass ends_after=<today> on /classes unless you want history.
  • Don't invent fields. If it isn't in the reference, it isn't there.
  • Capacity is live. capacity.spots_remaining counts seats as of the request. Show "3 spots left", not a number you saved yesterday.

Caching and rate limits

Responses are private, no-store, so the browser won't cache them and neither does any CDN. Each page view costs one request per endpoint you call. That is fine for a schedule page; a widget that refetches every few seconds is not, and the 300-requests-per-minute-per-IP limit will stop it. If you need to go higher, cache on your own server with a secret key.