// Public API · v1

Koppel je eigen website aan Clay.

Hou je marketingsite zoals 'ie is — Webflow, WordPress, Framer of vanille HTML — en laat aanmeldingen, contactformulieren en het lesrooster rechtstreeks naar Clay stromen. Eén API-key per gym. Scoped. EU-gehost. Geen extra tools.

Wat je krijgt.

De Clay App is je dagelijkse werkplek (lessen, leden, betalingen). De Public API is de brug naar je eigen merk-website. Je werkt verder in de app — formulieren, prijzenblok en lesrooster blijven in sync.

Geen webhooks om uit te denken voor de basis: één POST vanuit jouw formulier en de lead landt direct in Clay → Acquisitie → Leads, met source, UTM-data en consent vastgelegd.

  • Tenant-scoped

    Elke API-key zit vast aan jouw organisatie. Geen verkeerde data naar een andere gym, ooit.

  • Scope per key

    Geef de Webflow-key alleen `lead:write`. Geef de pricing-page key alleen `plans:read`. Compromittering is nooit een totale overgave.

  • Browser-veilig

    Allow-list je website-origin per key. CORS-preflight is ingebouwd. Geen cookies, geen sessies — alleen de Bearer-key.

  • EU-only

    API draait op Vercel EU. Postgres in Frankfurt. Resend EU. AVG vanuit de architectuur.

Quickstart in 5 minuten.

  1. Step 1

    Maak een API-key

    Log in op cms.clayapp.nl/admin → Integraties → Tenant API keys → New. Kies de scopes (begin met `lead:write`), voeg je website-origin toe (bv. https://watchmangym.nl) en klik Save. De plaintext key zie je één keer — bewaar 'm in 1Password.

  2. Step 2

    Test de key

    Doe een GET naar /tenant met scope `tenant:read` om te bevestigen dat je key werkt. Je krijgt je organisatie-id en slug terug.

  3. Step 3

    Bouw je formulier

    Plak het JS-snippet hieronder in je Webflow / WordPress / Framer-pagina. Vervang `clay_live_…` door je key. POST naar /leads zodra het formulier wordt verstuurd.

  4. Step 4

    Bekijk de leads

    Open Clay admin → Acquisitie → Leads. Elke submission staat er, met IP, user-agent, UTM-data en de gebruikte key. Wijs toe aan een teamlid en converteer naar een lid wanneer je 'm hebt gesproken.

Endpoints.

POST/api/public/v1/leadsscope: lead:write

Een lead toevoegen

Vang aanmeldformulieren, proefles-aanvragen en contactformulieren op vanaf je eigen website. Organisatie wordt server-side afgeleid uit de key — vermeld 'm niet in de body.

// curl — POST /api/public/v1/leads
curl -X POST https://clayapp.nl/api/public/v1/leads \
  -H "Authorization: Bearer clay_live_xxxxxxxxYourSecretHere" \
  -H "Content-Type: application/json" \
  -d '{
    "email":             "lara@example.com",
    "first_name":        "Lara",
    "last_name":         "Janssen",
    "phone":             "+31612345678",
    "source":            "trial",
    "source_detail":     "Webflow / signup-block",
    "interest":          "trial",
    "message":           "Wil graag een proefles boeken",
    "consent_marketing": true,
    "locale":            "nl",
    "metadata":          { "utm_campaign": "spring-2026" }
  }'
// 200 OK
{
  "ok": true,
  "data": {
    "id": 1234,
    "status": "new",
    "createdAt": "2026-04-28T11:23:45.000Z"
  }
}
GET/api/public/v1/classesscope: classes:read

Lesrooster ophalen

Render je actuele lesrooster live op je marketingsite — geen handmatige sync. Alleen `active` lessen voor jouw tenant; trainer/locatie zijn samengevat tot naam + stad zodat je geen interne IDs hoeft te koppelen.

// curl — GET /api/public/v1/classes
curl "https://clayapp.nl/api/public/v1/classes?from=2026-05-01T00:00:00Z&limit=20" \
  -H "Authorization: Bearer clay_live_xxxxxxxxYourSecretHere"
GET/api/public/v1/membership-plansscope: plans:read

Abonnementen ophalen

Render je prijzenblok dynamisch — pas één keer iets aan in Clay en je website is direct in sync. Inclusief `access_model` (credits / flat fee), `credits_per_period`, `billing_interval` en bedrag in EUR.

// curl — GET /api/public/v1/membership-plans
curl https://clayapp.nl/api/public/v1/membership-plans \
  -H "Authorization: Bearer clay_live_xxxxxxxxYourSecretHere"
GET/api/public/v1/tenantscope: tenant:read

Health check / introspectie

Bevestig welke organisatie + key gebruikt wordt. Handig voor een first-deploy smoke test of een no-code tool die wil weten welke shop er onder zit.

CORS & browser-gebruik.

Wil je de API rechtstreeks vanuit je website-JavaScript aanroepen (zonder eigen backend ertussen)? Voeg de origin van je site toe aan de allow-list van de key. Zonder allow-list werkt de key alleen server-to-server.

  • Een origin is scheme + host (+ port) zonder pad: `https://watchmangym.nl`, niet `https://watchmangym.nl/contact`.
  • Voeg meerdere origins toe als je staging hebt: `https://watchmangym.webflow.io` én `https://watchmangym.nl`.
  • Preflight (OPTIONS) wordt automatisch beantwoord — geen extra config nodig.
  • Zet je server-to-server keys in een aparte key zónder allow-list, en bewaar ze alleen in je backend env vars. Browser-keys hebben een allow-list nodig.

Foutcodes.

Alle errors gebruiken hetzelfde envelope: `{ ok: false, error: { code, message, details? } }`. Schrijf je client tegen `code` (machine-readable), niet tegen `message` (kan veranderen).

codestatusbetekenis
missing_authorization401Header ontbreekt
invalid_key_format401Key heeft geen `clay_live_…` formaat
invalid_key401Key niet gevonden of secret klopt niet
key_revoked401Key is ingetrokken in de admin
key_expired401Key heeft een expiry-datum gehad
origin_not_allowed403Browser-origin niet in allow-list
missing_scope403Key mist de vereiste scope (zie `details`)
validation_failed400Body voldoet niet aan het schema (zie `details.fieldErrors`)
rate_limited429Te veel requests — respecteer `Retry-After`
auth_unavailable503Tijdelijke storing op de auth-laag — retry

Recipes.

Webflow / WordPress / Framer — vanilla JS

Drop dit in een Custom Code embed. Werkt 1:1 in elk no-code tool dat een form-event afvangt.

// html / javascript
<form id="trial-form">
  <input name="email" type="email" required />
  <input name="first_name" />
  <input name="phone" />
  <button type="submit">Boek proefles</button>
</form>

<script>
  const CLAY_KEY = "clay_live_xxxxxxxxYourSecretHere";

  document.querySelector("#trial-form").addEventListener("submit", async (e) => {
    e.preventDefault();
    const fd = new FormData(e.currentTarget);
    const res = await fetch("https://clayapp.nl/api/public/v1/leads", {
      method: "POST",
      headers: {
        "Authorization": "Bearer " + CLAY_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        email:        fd.get("email"),
        first_name:   fd.get("first_name"),
        phone:        fd.get("phone"),
        source:       "trial",
        source_detail: location.pathname,
        consent_marketing: true,
        locale:       "nl",
        metadata: {
          utm_source:   new URL(location.href).searchParams.get("utm_source"),
          utm_campaign: new URL(location.href).searchParams.get("utm_campaign"),
        },
      }),
    });
    const json = await res.json();
    if (json.ok) {
      e.currentTarget.reset();
      alert("Bedankt — we mailen binnen één werkdag.");
    } else {
      alert("Oeps: " + json.error.message);
    }
  });
</script>

Zapier — Webhook → Clay

Connect alles wat een Zapier-trigger heeft (Typeform, Tally, Calendly cancellations) aan Clay zonder een regel code te schrijven.

// Zapier action — Webhooks by Zapier · POST
URL:     https://clayapp.nl/api/public/v1/leads
Method:  POST
Headers:
  Authorization: Bearer clay_live_xxxxxxxxYourSecretHere
  Content-Type:  application/json
Data:
  email:           {{trigger.email}}
  first_name:      {{trigger.first_name}}
  phone:           {{trigger.phone}}
  source:          zapier
  source_detail:   "Typeform: Open dag aanmelding"
  consent_marketing: true

Server-to-server — Node 20+

Voor backends die hun eigen logica laten lopen voor de POST naar Clay. Plan: doe key-rotatie via env vars en monitor `last_used_at` in de admin.

// node
const res = await fetch("https://clayapp.nl/api/public/v1/leads", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.CLAY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ email, first_name: firstName, source: "website" }),
});
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`Clay ${error.code}: ${error.message}`);
}

Klaar om te koppelen?

Maak je eerste API-key aan in 30 seconden — geen creditcard nodig. Werkt op alle Clay-plannen.