CombatScore
Features
Find a GymPricingAboutBlogSign inGet Premium
Getting StartedAuthenticationZapier TriggersZapier ActionsWebhooksWebhook EventsWebhook ManagementAPI KeysRate LimitsError HandlingSDK ExamplesOpenAPI Spec

Developer API

Integrate CombatScore with your tools using our REST API and webhook system.

Base URL: https://combatscore.app · API Version: 2026-04-16

Getting StartedAuthenticationZapier TriggersZapier ActionsWebhooksWebhook EventsWebhook ManagementAPI KeysRate LimitsError HandlingSDK ExamplesOpenAPI Spec

Getting Started

Three steps to your first API call:

  1. Create an API key — go to your gym dashboard at Dashboard → Settings → API Keys and generate a key. Copy it immediately — it's shown only once.
  2. Make your first request:
    curl
    curl -H "Authorization: Bearer cs_your_key_here" \
      https://combatscore.app/api/zapier/triggers?type=new_member
  3. Set up a webhook (optional) — subscribe to real-time events instead of polling:
    curl
    curl -X POST -H "Authorization: Bearer cs_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{"url":"https://your-app.com/webhooks/combatscore","events":["member.joined","payment.received"]}' \
      https://combatscore.app/api/gym/webhooks

Authentication

All API requests require an API key passed via the Authorization header:

http
Authorization: Bearer cs_your_api_key_here

Keys are scoped: read for polling triggers and GET endpoints, write for actions that create or modify data. A key can have both scopes.

Keys are prefixed with cs_ followed by 64 hex characters. They are hashed (SHA-256) on our side — if you lose the key, generate a new one.

Zapier Triggers (Polling)

Poll these endpoints to detect new events. Returns the latest 20 items, sorted newest first.

GET/api/zapier/triggers?type=new_memberscope: read

Returns the 20 most recent gym members.

FieldTypeDescription
iduuidMember record ID
user_idstringClerk user ID
rolestringmember or coach
joined_atdatetimeWhen they joined
GET/api/zapier/triggers?type=new_paymentscope: read

Returns the 20 most recent payments (invoices).

FieldTypeDescription
iduuidInvoice ID
user_idstringClerk user ID
amount_centsintegerAmount in cents
statusstringPayment status
created_atdatetimePayment date
GET/api/zapier/triggers?type=new_sessionscope: read

Returns the 20 most recent training sessions logged by gym members.

FieldTypeDescription
iduuidSession ID
user_idstringClerk user ID
session_typestringgi, no-gi, open-mat, etc.
datedateSession date
duration_minsintegerDuration in minutes
GET/api/zapier/triggers?type=new_checkinscope: read

Returns the 20 most recent kiosk check-ins.

FieldTypeDescription
iduuidAttendance record ID
user_idstringClerk user ID
class_schedule_iduuid | nullLinked class (if any)
statusstringapproved or pending

Zapier Actions

Create resources in your gym. Requires a write scope API key.

POST/api/zapier/actions?type=create_leadscope: write

Add a new lead to your gym's CRM.

FieldTypeDescription
first_name *stringLead's first name (required)
last_namestringLead's last name
emailstringEmail address
phonestringPhone number
sourcestringLead source (default: zapier)
curl
curl -X POST -H "Authorization: Bearer cs_..." \
  -H "Content-Type: application/json" \
  -d '{"first_name":"John","last_name":"Doe","email":"john@example.com","source":"website"}' \
  https://combatscore.app/api/zapier/actions?type=create_lead
POST/api/zapier/actions?type=send_smsscope: write

Queue an outbound SMS message.

FieldTypeDescription
phone *stringE.164 format (e.g. +15551234567)
message *stringMessage body (max 1600 chars)
curl
curl -X POST -H "Authorization: Bearer cs_..." \
  -H "Content-Type: application/json" \
  -d '{"phone":"+15551234567","message":"Welcome to our gym!"}' \
  https://combatscore.app/api/zapier/actions?type=send_sms
POST/api/zapier/actions?type=log_attendancescope: write

Log a manual attendance check-in for a member.

FieldTypeDescription
user_id *stringClerk user ID of the member
curl
curl -X POST -H "Authorization: Bearer cs_..." \
  -H "Content-Type: application/json" \
  -d '{"user_id":"user_abc123"}' \
  https://combatscore.app/api/zapier/actions?type=log_attendance

Webhooks (Push)

Subscribe to real-time events instead of polling. Each delivery is signed with HMAC-SHA256 using your webhook secret. Verify the signature via the X-CombatScore-Signature header.

Delivery Headers

FieldTypeDescription
X-CombatScore-SignaturestringHMAC-SHA256 hex digest of the JSON body
X-CombatScore-EventstringEvent type (e.g. member.joined)
Content-Typestringapplication/json

Payload Shape

json
{
  "event": "member.joined",
  "gym_id": "abc-123",
  "data": { ... },
  "timestamp": "2026-04-16T12:00:00.000Z"
}

Signature Verification

javascript
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body) // raw JSON string, not parsed
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Deliveries time out after 10 seconds. Failed deliveries are logged but not retried automatically. Check your delivery history via the webhook management API.

Webhook Event Catalog

Eight event types with sample payloads:

member.joined

Fired when a new member joins your gym.

json
{
  "event": "member.joined",
  "gym_id": "gym_abc123",
  "data": {
    "user_id": "user_xyz789"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

member.left

Fired when a member leaves or is removed from your gym.

json
{
  "event": "member.left",
  "gym_id": "gym_abc123",
  "data": {
    "user_id": "user_xyz789"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

payment.received

Fired when a membership payment completes via Stripe.

json
{
  "event": "payment.received",
  "gym_id": "gym_abc123",
  "data": {
    "user_id": "user_xyz789",
    "plan_id": "plan_456",
    "amount_cents": 9900
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

session.logged

Fired when a member logs a training session.

json
{
  "event": "session.logged",
  "gym_id": "gym_abc123",
  "data": {
    "user_id": "user_xyz789",
    "session_id": "sess_001",
    "session_type": "no-gi",
    "date": "2026-04-16",
    "duration_mins": 90
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

attendance.checked_in

Fired when a member checks in at the kiosk.

json
{
  "event": "attendance.checked_in",
  "gym_id": "gym_abc123",
  "data": {
    "user_id": "user_xyz789",
    "attendance_id": "att_001",
    "status": "approved"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

class.scheduled

Fired when a new class is added to the schedule.

json
{
  "event": "class.scheduled",
  "gym_id": "gym_abc123",
  "data": {
    "schedule_id": "sched_001",
    "title": "Advanced No-Gi",
    "discipline": "no-gi",
    "day_of_week": 2,
    "start_time": "18:00"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

lead.created

Fired when a new lead is added (via API, Zapier, or manually).

json
{
  "event": "lead.created",
  "gym_id": "gym_abc123",
  "data": {
    "lead_id": "lead_001",
    "first_name": "John",
    "last_name": "Doe",
    "source": "zapier"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

contract.signed

Fired when a member signs a membership contract.

json
{
  "event": "contract.signed",
  "gym_id": "gym_abc123",
  "data": {
    "contract_id": "contract_001",
    "user_id": "user_xyz789"
  },
  "timestamp": "2026-04-16T14:30:00.000Z"
}

Webhook Management API

CRUD endpoints for managing webhook subscriptions programmatically. Requires Clerk session auth (gym owner).

GET/api/gym/webhooksscope: owner

List all webhook subscriptions for your gym.

json
{
  "webhooks": [
    {
      "id": "wh_001",
      "url": "https://your-app.com/webhooks",
      "events": ["member.joined", "payment.received"],
      "is_active": true,
      "created_at": "2026-04-16T12:00:00.000Z"
    }
  ]
}
POST/api/gym/webhooksscope: owner

Create a new webhook subscription. The signing secret is returned once.

FieldTypeDescription
url *stringHTTPS endpoint to receive events
eventsstring[]Event types to subscribe to (empty = all)
PATCH/api/gym/webhooks/{id}scope: owner

Update a webhook's URL, events, or active status.

FieldTypeDescription
urlstringNew HTTPS endpoint
eventsstring[]Updated event filter
is_activebooleanEnable or disable
DELETE/api/gym/webhooks/{id}scope: owner

Deactivate a webhook subscription.

API Key Management

Manage API keys programmatically. Requires Clerk session auth (gym owner).

GET/api/gym/api-keysscope: owner

List all API keys. Shows prefixes only — full keys cannot be retrieved.

json
{
  "keys": [
    {
      "id": "key_001",
      "name": "Zapier Production",
      "key_prefix": "cs_a1b2c3d4",
      "scopes": ["read", "write"],
      "last_used": "2026-04-15T10:00:00.000Z",
      "is_active": true,
      "created_at": "2026-03-01T12:00:00.000Z"
    }
  ]
}
POST/api/gym/api-keysscope: owner

Generate a new API key. The full key is returned once — store it securely.

FieldTypeDescription
namestringDisplay name (default: Default)
scopesstring[]Permissions: read, write (default: [read])

Rate Limits

EndpointLimit
Zapier triggers60 requests/minute per gym
Zapier actions30 requests/minute per gym
Standard mutations30 requests/minute per user
Destructive operations5 per hour per user

Rate-limited responses return 429 Too Many Requests with the standard error format.

Error Handling

All errors follow the same shape:

json
{ "error": "Human-readable error message" }
StatusMeaning
400Bad request — missing or invalid parameters
401Unauthorized — invalid or missing API key
403Forbidden — insufficient scope or not a gym owner
404Not found
409Conflict — resource already exists
429Rate limit exceeded
500Internal server error

SDK Examples

JavaScript (Node.js / fetch)

javascript
const API_KEY = "cs_your_key_here";
const BASE    = "https://combatscore.app";

// Poll for new members
const members = await fetch(`${BASE}/api/zapier/triggers?type=new_member`, {
  headers: { Authorization: `Bearer ${API_KEY}` },
}).then(r => r.json());

// Create a lead
const lead = await fetch(`${BASE}/api/zapier/actions?type=create_lead`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    first_name: "Jane",
    last_name: "Doe",
    email: "jane@example.com",
  }),
}).then(r => r.json());

Python (requests)

python
import requests

API_KEY = "cs_your_key_here"
BASE    = "https://combatscore.app"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Poll for new members
members = requests.get(
    f"{BASE}/api/zapier/triggers",
    params={"type": "new_member"},
    headers=HEADERS,
).json()

# Create a lead
lead = requests.post(
    f"{BASE}/api/zapier/actions",
    params={"type": "create_lead"},
    headers={**HEADERS, "Content-Type": "application/json"},
    json={"first_name": "Jane", "last_name": "Doe", "email": "jane@example.com"},
).json()

Ruby

ruby
require "net/http"
require "json"
require "uri"

API_KEY = "cs_your_key_here"
BASE    = "https://combatscore.app"

# Poll for new members
uri = URI("#{BASE}/api/zapier/triggers?type=new_member")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{API_KEY}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
members = JSON.parse(res.body)

Webhook Receiver (Express.js)

javascript
const express = require("express");
const crypto  = require("crypto");

const app = express();
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

const WEBHOOK_SECRET = "your_webhook_secret";

app.post("/webhooks/combatscore", (req, res) => {
  const signature = req.headers["x-combatscore-signature"];
  const expected  = crypto.createHmac("sha256", WEBHOOK_SECRET)
    .update(req.rawBody)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send("Bad signature");
  }

  const { event, data } = req.body;
  console.log("Received event:", event, data);

  res.sendStatus(200);
});

app.listen(3000);

OpenAPI Specification

A machine-readable OpenAPI 3.0 spec is available for generating client SDKs or importing into Postman, Insomnia, or Swagger UI:

url
https://combatscore.app/openapi.json

Download or import the URL directly into your tool of choice. The spec covers all Zapier, webhook, and API key management endpoints with full request/response schemas.

API Version: 2026-04-16 • developers@combatscore.app
CombatScore

Data-driven AI coaching for combat sports athletes, coaches, and gym owners.

Veteran-owned & operated

Product

  • Features
  • Pricing
  • AI for athletes
  • AI for gyms
  • Changelog

Discover

  • Find a gym
  • Travelling? Drop in
  • Find a coach
  • Blog
  • State of BJJ

Company

  • About
  • FAQ
  • Help center
  • Developers
  • Status
  • Report a concern

Legal

  • Privacy
  • Terms
  • Legal index
  • Safety center
support@combatscore.appInstagramX / Twitter
Payments secured by StripeData encrypted at restRow-level security on all dataHosted on Supabase (SOC 2 Type II)

© 2026 CombatScore. All rights reserved.

CombatScore