Developer

API Reference

The VERVE Pulse REST API reads AND writes your gym’s data programmatically. Available on the Pro plan.

Base URL: https://app.vervepulse.io/api/v1
Format: JSON
Auth: Bearer token
Read limit: 120 req/min per key
Write limit: 1,000 req/hour per key

Authentication

All API requests must include your API key in the Authorization header (or an X-API-Key header). Generate your API key from Settings → Open API.

curl https://app.vervepulse.io/api/v1/members \
  -H "Authorization: Bearer vp_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json"

Keep your API key secret. Do not expose it in client-side code or public repositories. Rotate compromised keys immediately from Settings → Open API — revoking one takes effect immediately.

Every key can read. A key only gets the endpoints marked WRITE SCOPE below if you tick “Allow write access” when you create it — a read-only key gets a 403 from every one of them. Every request is scoped to the gym that owns the key, regardless of anything in the request body or query string.

Endpoints

Members
GET/api/v1/members
List members — filter by status or email
GET/api/v1/members/:id
Get one member
POST/api/v1/membersWRITE SCOPE
Create a member — first_name and last_name required
PATCH/api/v1/membersWRITE SCOPE
Update a member — id in the request body
Classes
GET/api/v1/classes
List classes — since=<ISO date> for upcoming
GET/api/v1/classes/:id
Get one class
Bookings
GET/api/v1/bookings
List bookings — filter by status, class_id, member_id
GET/api/v1/bookings/:id
Get one booking
POST/api/v1/bookingsWRITE SCOPE
Book a member into a class (class_id, member_id, spot_number?)
DELETE/api/v1/bookings/:idWRITE SCOPE
Cancel a booking — auto-promotes the next waitlisted member
Check-ins
GET/api/v1/checkins
List check-ins — since=<ISO date> for incremental pulls
GET/api/v1/checkins/:id
Get one check-in
POST/api/v1/checkinsWRITE SCOPE
Record a check-in (member_id) — tagged method: 'api'
Invoices
GET/api/v1/invoices
List invoices — filter by status or member_id
GET/api/v1/invoices/:id
Get one invoice
Plans & Leads
GET/api/v1/plans
Membership plans
GET/api/v1/plans/:id
Get one plan
GET/api/v1/leads
List leads — filter by status
GET/api/v1/leads/:id
Get one lead

Every list endpoint takes limit (default 100, max 200) and offset, and responds with{ data, limit, offset, has_more }. The full machine-readable catalogue (columns, filters per resource) is always up to date at GET /api/v1 — no key required.

Writing data

Write endpoints run through the exact same validation as the matching action in the Pulse dashboard — the same required fields, the same guardrails (e.g. a member’s plan/location must belong to your gym, a banned member cannot be booked or checked in) — never a separate, looser path. A foreign id (someone else’s member, class or booking) always returns a plain 404, never another gym’s data.

curl -X POST https://app.vervepulse.io/api/v1/members \
  -H "Authorization: Bearer vp_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Alex", "last_name": "Johnson", "email": "alex@example.com"}'

Webhooks

VERVE Pulse can push webhook events to your own HTTPS endpoint when things happen in the gym. Configure endpoints in Settings → Open API → Outbound webhooks. Deliveries are queued and sent by a job that runs about once a minute, not the instant the event happens.

member.createdA new member was added
member.updatedA member’s profile, plan or status was changed
booking.createdA member was booked into a class (booked or waitlisted)
booking.cancelledA booking was cancelled
checkin.createdA member checked in
invoice.paidA membership invoice was paid
invoice.payment_failedA membership invoice payment failed

Example payload:

{
  "event": "member.created",
  "created_at": "2026-07-28T06:00:00Z",
  "data": {
    "id": "8f1e2c3a-...",
    "first_name": "Alex",
    "last_name": "Johnson",
    "email": "alex@example.com",
    "status": "active",
    "created_at": "2026-07-28T06:00:00Z"
  }
}

Every delivery carries X-Pulse-Timestamp (unix seconds) andX-Pulse-Signature: a hex HMAC-SHA256 oftimestamp + "." + rawBody, signed with the endpoint’s own secret (shown once, when you create the endpoint). Check both before trusting the payload: the signature proves it came from us, the timestamp proves it is not an old delivery being replayed at you.

const crypto = require('crypto');

function isValidPulseDelivery(rawBody, timestampHeader, signatureHeader, endpointSecret) {
  // Reject anything older than 5 minutes so a captured delivery can't be replayed.
  const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestampHeader));
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;

  const expected = crypto.createHmac('sha256', endpointSecret)
    .update(timestampHeader + '.' + rawBody)
    .digest('hex');
  // Constant-time comparison — never a plain === on secret-derived values.
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

A failed delivery (timeout, non-2xx response, or a redirect — redirects are never followed) is retried with backoff: 1 minute, 10 minutes, 1 hour, then 6 hours, for up to 5 attempts total. After that it’s marked failed. An endpoint that racks up 10 failed deliveries in a row (no successful delivery in between) is automatically disabled and the gym is emailed about it — re-enable it from Settings → Open API once the receiving end is fixed. Recent deliveries and a “Redeliver” button live on the same settings page.

Endpoint URLs must be https:// and cannot resolve to localhost or a private/internal IP address — checked both when you save the endpoint and again on every send, so a hostname that gets re-pointed at internal infrastructure later is still blocked.

Rate Limits & Errors

Reads are capped at 120 requests per minute per API key; writes at 1,000 requests per hour per API key. Every response carriesX-RateLimit-Limit,X-RateLimit-Remaining andX-RateLimit-Reset headers. Exceeding the limit returns 429 Too Many Requests.

200OK — request succeeded
201Created — resource was created
400Bad Request — invalid or missing parameters
401Unauthorized — invalid, missing or revoked API key
403Forbidden — API not on your plan, or this key lacks the write scope
404Not Found — resource does not exist, or belongs to another gym
409Conflict — e.g. member already booked, or that spot is taken
429Too Many Requests — rate limit exceeded
500Server Error — contact support

Need help with the API?

API access is available on the Pro plan ($549/mo, including GST). Contact us for enterprise rate limits or custom integration support.

Contact developer support →