Back to Documentation

API Access

Programmatically access your event data

Set up with AI in seconds

Copy a ready-made prompt and paste it into Lovable, Cursor, Replit, ChatGPT, or Claude to scaffold your integration.

Getting an API Key

  1. Go to Admin Dashboard → Settings
  2. Scroll to the API Keys section
  3. Click "Generate New Key" and give it a descriptive name
  4. Copy the generated key immediately — it is only shown once
⚠️ Security: Treat your API key like a password. Do not commit it to version control or share it publicly. Rotate keys periodically.

Authentication

All requests must include your API key in the x-api-key header:

cURL
curl -H "x-api-key: knmi_your_key_here" \
  "https://hbyaeoqwllejvvcajxkd.supabase.co/functions/v1/event-data-api?resource=event"

Base URL

https://hbyaeoqwllejvvcajxkd.supabase.co/functions/v1/event-data-api

Available Resources

Each API key is scoped to your organization. By default, every resource returns data across all events in your org. Add the optional event_id filter only when you want to narrow to a single event.

event

All events in your organization (or one event when event_id is passed).

Org-wide example:
GET ?resource=event
Filter to one event:
GET ?resource=event&event_id=YOUR_EVENT_ID
Response fields:

id, event_name, event_date, event_end_date, venue_name, venue_city, timezone, max_attendees, is_public

participants

All registered participants across every event in your org.

Org-wide example:
GET ?resource=participants
Filter to one event:
GET ?resource=participants&event_id=YOUR_EVENT_ID
Response fields:

id, full_name, email, phone, company, job_title, status, ticket_type, event_id, group_assignment, track_assignment, created_at, updated_at

sessions

All sessions across every event in your org.

Org-wide example:
GET ?resource=sessions
Filter to one event:
GET ?resource=sessions&event_id=YOUR_EVENT_ID
Response fields:

id, title, description, start_time, end_time, location, track, zone, status, session_code, event_id, event_day_id, max_participants, current_participants

tickets

All ticket types across every event in your org.

Org-wide example:
GET ?resource=tickets
Filter to one event:
GET ?resource=tickets&event_id=YOUR_EVENT_ID
Response fields:

id, name, price, currency, quantity_total, quantity_sold, is_active, event_id

checkins

All check-in / admission tickets across every event in your org.

Org-wide example:
GET ?resource=checkins
Filter to one event:
GET ?resource=checkins&event_id=YOUR_EVENT_ID
Response fields:

id, full_name, email, ticket_number, checked_in, checked_in_at, status, event_id

Response Format

All responses are JSON with the following structure:

{
  "data": [...],
  "resource": "participants",
  "organization_id": "uuid",
  "pagination": {
    "limit": 1000,
    "offset": 0,
    "count": 1000,
    "has_more": true,
    "next_offset": 1000
  }
}

Lists return at most 1000 rows per request, oldest first. Add &limit= (1 to 1000) and &offset=, and keep requesting next_offset while has_more is true.

JavaScript Example

JavaScript / Node.js
const API_KEY = "knmi_your_key_here";
const BASE_URL = "https://hbyaeoqwllejvvcajxkd.supabase.co/functions/v1/event-data-api";

// Fetches every page of a resource (lists return up to 1000 rows per call).
async function getAll(resource, eventId) {
  const rows = [];
  let offset = 0;
  while (true) {
    const params = new URLSearchParams({ resource, limit: "1000", offset: String(offset) });
    if (eventId) params.set("event_id", eventId);

    const response = await fetch(`${BASE_URL}?${params}`, {
      headers: { "x-api-key": API_KEY },
    });
    if (response.status === 429) {
      const wait = Number(response.headers.get("Retry-After") ?? "60");
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`API error: ${response.status}`);

    const { data, pagination } = await response.json();
    rows.push(...data);
    if (!pagination?.has_more) return rows;
    offset = pagination.next_offset;
  }
}

// Usage
const participants = await getAll("participants", "your-event-id");
console.log(`Found ${participants.length} participants`);

Error Codes

StatusMeaning
401Missing or invalid API key
400Missing or unknown resource parameter
405HTTP method not allowed (use GET)
500Internal server error

Webhooks

Webhooks let you receive real-time HTTP POST notifications when events happen in your organization.

Setup

  1. Go to Admin Dashboard → Settings → Webhooks
  2. Click "Add Webhook"
  3. Enter your endpoint URL and select which events to subscribe to
  4. A signing secret is generated automatically — use it to verify payloads

Available Events

EventDescription
participant.createdNew participant registered
participant.approvedParticipant approved
ticket.purchasedTicket purchased
checkin.completedAttendee checked in
session.createdSession created
session.updatedSession updated
application.submittedApplication submitted
application.reviewedApplication reviewed

Payload Format

{
  "id": "5f0c6f0e-…",            // stable across retries: de-duplicate on it
  "event": "participant.created",
  "timestamp": "2026-04-15T12:00:00.000Z",
  "organization_id": "uuid",
  "data": {
    "participant_id": "uuid",
    "event_id": "uuid",
    "full_name": "Jane Doe",
    "email": "jane@example.com",
    "status": "pending"
  }
}

Delivery and Retries

  • Endpoints must use https:// and a public host name or address. Private, loopback and internal addresses are refused.
  • Respond with any 2xx status within 10 seconds. Redirects are not followed and count as a failed attempt.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours, then marked failed.
  • Delivery is at-least-once, so the same event can arrive twice. Use the id field (also sent as the X-Webhook-Id header) to ignore duplicates.
  • Events are usually delivered within a minute or two of the change. Order is not guaranteed.

Verifying Signatures

Each webhook request includes an X-Webhook-Signature header — an HMAC-SHA256 hex digest of the request body using your signing secret.

import crypto from "crypto";

function verifySignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  const a = Buffer.from(signature ?? "", "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Verify against the raw request body, before JSON parsing.

Rate Limiting

The API enforces a rate limit of 100 requests per minute per API key. Every response includes rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1713200160
  • X-RateLimit-Limit — Maximum requests allowed per window
  • X-RateLimit-Remaining — Requests remaining in the current window
  • X-RateLimit-Reset — Unix timestamp when the window resets

If you exceed the limit, you'll receive a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait.

Best Practices

  • Use the event_id filter to reduce response size when you only need data for one event
  • Cache responses where appropriate to minimize API calls
  • Respect rate limit headers — back off when X-RateLimit-Remaining is low
  • Store your API key in environment variables, not in source code
  • Create separate keys for different integrations so you can revoke them independently
  • Monitor the "Last used" timestamp in your admin settings to track key usage