Developer
API Reference
The VERVE Pulse REST API reads AND writes your gym’s data programmatically. Available on the Pro plan.
https://app.vervepulse.io/api/v1JSONBearer token120 req/min per key1,000 req/hour per keyAuthentication
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
/api/v1/members/api/v1/members/:id/api/v1/membersWRITE SCOPE/api/v1/membersWRITE SCOPE/api/v1/classes/api/v1/classes/:id/api/v1/bookings/api/v1/bookings/:id/api/v1/bookingsWRITE SCOPE/api/v1/bookings/:idWRITE SCOPE/api/v1/checkins/api/v1/checkins/:id/api/v1/checkinsWRITE SCOPE/api/v1/invoices/api/v1/invoices/:id/api/v1/plans/api/v1/plans/:id/api/v1/leads/api/v1/leads/:idEvery 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 addedmember.updatedA member’s profile, plan or status was changedbooking.createdA member was booked into a class (booked or waitlisted)booking.cancelledA booking was cancelledcheckin.createdA member checked ininvoice.paidA membership invoice was paidinvoice.payment_failedA membership invoice payment failedExample 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 succeeded201Created — resource was created400Bad Request — invalid or missing parameters401Unauthorized — invalid, missing or revoked API key403Forbidden — API not on your plan, or this key lacks the write scope404Not Found — resource does not exist, or belongs to another gym409Conflict — e.g. member already booked, or that spot is taken429Too Many Requests — rate limit exceeded500Server Error — contact supportNeed 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 →