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 developer secret from Settings → Developer 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. Refresh compromised secrets from Settings → Developer API. The previous secret stops working immediately; update your integration with the new one.

Choose Lead submissions only for website enquiries. This grants leads:write without access to read gym data. Choose read access or read and write access for broader integrations. Endpoints marked WRITE SCOPE require broad write access. Every request is scoped to the gym that owns the secret.

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. allowed_plan_ids lists the plans that may book (empty = every plan)
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?, override?). Holds the rules members book under, each a 403 with its code: plan_not_eligible, home_location_only, staff_booking_only, booking_not_open (with bookable_from) and booking_requires_approval. override: true books the member anyway, as staff can
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
POST/api/v1/leadsLEADS:WRITE OR WRITE
Submit or correct a website enquiry — external_id required

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.

Website lead submissions

Send POST /api/v1/leads from your server with a lead-submission secret. Include a stable external_id, a name (or first and last name), and an email or phone number. Send child details, lesson preferences or other enquiry information in message. Pulse displays the website message separately from staff notes.

First submission returns 201. Repeating the same external id with the same integration returns 200 and the same lead id; changed fields correct the enquiry, and an identical retry preserves staff edits. Refreshing a secret keeps its submission history. A deleted enquiry returns 410. Send corrections in order and use a new external id for a new enquiry.

Swagger documentation and test requests · OpenAPI specification

Writing data

Member, booking and check-in writes run through the same validation as the matching action in the Pulse dashboard — the same required fields and 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"}'

A member created this way starts as pending, the same rule as the dashboard’s Add Member wizard: they cannot check in or sign in to the member app until a subscription is linked to them. For a member you bill outside Pulse, send "pays_manually": true and they start active straight away.

Webhooks

VERVE Pulse can push webhook events to your own HTTPS endpoint when things happen in the gym. Configure endpoints in Settings → Developer 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 plus GST). Contact us for enterprise rate limits or custom integration support.

Contact developer support →