Pewpros API Documentation
A friendly overview of the JSON API that powers your events, courses, memberships, groups, community, and CRM data. Use this reference to build integrations, sync external systems, or power custom client apps.
Introduction
Welcome to the developer API. Every endpoint listed here is served under
/api/v1
on your domain. Requests and responses use JSON, and authentication is
performed with a Bearer token.
If you prefer an interactive view, jump over to the Swagger UI where every route is listed with a live "Try it out" panel. A raw OpenAPI 3 spec is available at /api/docs/openapi.json for importing into Postman, Insomnia, or your code generator of choice.
Authentication
Most endpoints require a Bearer token. Generate one from your account
settings, then send it on every request in the
Authorization
header.
curl -X GET "https://your-domain.example.com/api/v1/events" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Tokens are long-lived by default. Treat them like passwords: never commit them to source control, rotate them when a team member leaves, and scope them to the smallest set of permissions you need.
{
"error": "unauthorized"
}
GET /api/v1/instance
and the public blog routes)
do not require authentication. The rest return
401
without a valid token.
Conventions
All requests with a body must set Content-Type: application/json. Field names are
snake_case. Timestamps are ISO 8601 in UTC (for example, 2026-06-15T09:00:00Z).
Money values are expressed in cents as integers to avoid floating point
rounding.
curl -X POST "https://your-domain.example.com/api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe"
}'
Collection endpoints accept
page
and per_page
query parameters for pagination. Where a resource has a human-friendly
slug (events, courses) you can fetch it either by numeric id or by slug.
Partner API
The Partner API
is a secret-key, server-to-server surface
for facility-management integrators (POS, CRM, and booking systems). It
exposes the facility entities a partner needs — customers and membership
tiers, range bookings, bookable resources, scheduling and rosters, and
orders — under a single versioned base path,
/api/v1/partner
. Every endpoint is browsable with a live "Try it out" panel in the
Swagger UI
under the
partner
tag.
Authentication
Partner endpoints authenticate with a secret
X-Partner-Key
header (format sk_partner_...),
generated and rotated by an admin in Site Settings → Integrations. This is a server-to-server
secret — unlike the public funnel key
(pk_...),
it must never be embedded in client-delivered HTML, and the public key is
rejected here. The partner API allows 600 requests per minute per source IP —
enough to fully back-fill a multi-thousand-record tenant across every list
endpoint in a single run. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining,
and
X-RateLimit-Reset
headers; a 429
adds Retry-After
(seconds) so you can back off and resume from your last cursor cleanly.
curl -X GET "https://your-domain.example.com/api/v1/partner/customers" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"
{
"data": [
{
"id": 1024,
"first_name": "Pat",
"last_name": "Member",
"email": "pat@example.com",
"phone": "+13135550100",
"status": "active",
"membership": {"tier_id": 4, "tier_name": "Gold", "tier_slug": "gold"},
"waiver": {
"status": "signed",
"signed_at": "2026-03-01T00:00:00Z",
"expires_at": "2027-03-01T00:00:00Z"
},
"inserted_at": "2026-01-12T14:03:00Z",
"updated_at": "2026-05-15T17:13:44Z"
}
],
"meta": {"page": 1, "per_page": 50, "total": 1}
}
401
on every partner route. The key is compared in constant time.
Pagination & incremental sync
Every list endpoint accepts
page
and per_page
(default 50, max 100) and returns a
meta
block with page, per_page, and total.
Pass
updated_since
(ISO 8601) to fetch only rows changed at or after that time — results are
ordered oldest-change-first, so you advance a cursor to the last
updated_at
you saw and page forward without missing or re-processing a row. Money is
always integer cents.
# Page 1: everything changed since your last cursor.
curl -X GET "https://your-domain.example.com/api/v1/partner/memberships?updated_since=2026-06-01T00:00:00Z&per_page=100" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"
# Remember the largest updated_at you saw, then page forward with it.
curl -X GET "https://your-domain.example.com/api/v1/partner/memberships?updated_since=2026-06-14T09:12:33Z&per_page=100" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"
Pagination and incremental sync compose: hold an
updated_since
cursor steady and walk
page
forward until a page returns fewer than
per_page
rows. Each response's
meta
block (page, per_page, total)
tells you where you are. After each run, persist the largest
updated_at
you saw as the next run's cursor — so a sync only ever processes
customers that actually changed, never the whole book.
# Incremental customer sync: pull ONLY changed customers, 100 at a time.
#
# - cursor = the largest `updated_at` you have processed so far
# (store it between runs; start with a far-past date on first run)
# - results come back oldest-change-first, so the last row of the last page
# is your new cursor
cursor="2026-06-01T00:00:00Z" # load from your last run; far-past on first run
page=1
while true; do
resp=$(curl -s \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY" \
"https://your-domain.example.com/api/v1/partner/customers?updated_since=${cursor}&per_page=100&page=${page}")
rows=$(echo "$resp" | jq '.data | length')
[ "$rows" -eq 0 ] && break # no more changed customers -> done
# ... upsert each row into your POS:
# - membership.tier_slug -> your discount tier
# - waiver.status / waiver.signed_at / waiver.expires_at
echo "$resp" | jq -c '.data[]' | while read -r customer; do
: # your_upsert "$customer"
done
[ "$rows" -lt 100 ] && break # short page -> last page, stop
page=$((page + 1))
done
# Persist the largest updated_at you saw as the next run's cursor.
updated_since
fixed for the whole loop and only advance it once the loop finishes. For a
one-time full backfill, page promptly (or run it during a quiet window) so
the offsets stay stable while you read.
Customers & memberships
/api/v1/partner/customers
List customers
Lean PII plus the customer's discount tier and waiver state inline. Supports search and updated_since.
/api/v1/partner/customers/:id
Retrieve a customer
A single customer by id.
/api/v1/partner/membership-levels
List membership tiers
All membership levels/tiers (id, name, slug, price_cents) to map to your POS discount rules.
/api/v1/partner/membership-levels/:id
Retrieve a membership tier
A single membership level.
/api/v1/partner/memberships
List memberships
Memberships with member identity, status, and tier block. Filter by status or membership_level_id.
/api/v1/partner/memberships/:id
Retrieve a membership
A single membership by id.
Range lanes & reservations
/api/v1/partner/range/lanes
List lanes
Active range lanes. Filter by location_id or active.
/api/v1/partner/range/lanes/:id/availability
Lane availability
Open start times for a lane. Query: date (required, YYYY-MM-DD), duration (minutes, default 60).
curl -X GET "https://your-domain.example.com/api/v1/partner/range/lanes/12/availability?date=2026-08-01&duration=60" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"
{
"data": {
"lane_id": 12,
"date": "2026-08-01",
"duration_minutes": 60,
"slots": [
"2026-08-01T13:00:00Z",
"2026-08-01T13:30:00Z",
"2026-08-01T14:00:00Z"
]
}
}
/api/v1/partner/range/reservations
List reservations
Reservations. Filters: lane_id, location_id, status, from, to, updated_since.
/api/v1/partner/range/reservations/:id
Retrieve a reservation
A single reservation by id.
/api/v1/partner/range/reservations
Create a reservation
Books a lane on behalf of a guest. Requires lane_id, starts_at, ends_at, and guest_name + guest_phone. Priced server-side.
curl -X POST "https://your-domain.example.com/api/v1/partner/range/reservations" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"lane_id": 12,
"starts_at": "2026-08-01T13:00:00Z",
"ends_at": "2026-08-01T14:00:00Z",
"guest_name": "Pat Guest",
"guest_phone": "+13135550100",
"shooter_count": 2
}'
{
"data": {
"id": 6,
"status": "reserved",
"source": "counter",
"lane_id": 12,
"starts_at": "2026-08-01T13:00:00Z",
"ends_at": "2026-08-01T14:00:00Z",
"shooter_count": 2,
"price_cents": 2500,
"customer": {"name": "Pat Guest", "phone": "+13135550100", "email": null, "user_id": null},
"cancellation_token": "r0c8X1...",
"inserted_at": "2026-06-25T19:40:00Z",
"updated_at": "2026-06-25T19:40:00Z"
}
}
/api/v1/partner/range/reservations/:id/cancel
Cancel a reservation
Cancels a reservation by id.
curl -X POST "https://your-domain.example.com/api/v1/partner/range/reservations/6/cancel" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"
source: "counter"
(which bypasses online-only policy gates) while still enforcing hours,
closures, capacity, turnover, and overlap. Pass
source: "online"
to opt into the stricter gates.
Bookable resources & bookings
/api/v1/partner/resources
List resources
Bookable resources (bays, rooms, simulators). Filter by location_id or active.
/api/v1/partner/resources/:id
Retrieve a resource
A single resource by id.
/api/v1/partner/resources/:id/availability
Resource availability
Capacity-aware slots. Query: date (required), duration, party_size.
/api/v1/partner/bookings
List bookings
Resource bookings. Filters: status, from, to, updated_since.
/api/v1/partner/bookings/:id
Retrieve a booking
A single booking by id.
/api/v1/partner/bookings
Create a booking
Books a resource. Requires resource_id, starts_at, ends_at. Priced server-side; members-only/waiver/age gates and capacity/conflict checks are enforced.
curl -X POST "https://your-domain.example.com/api/v1/partner/bookings" \
-H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource_id": 7,
"starts_at": "2026-08-01T13:00:00Z",
"ends_at": "2026-08-01T14:00:00Z",
"party_size": 2,
"customer_name": "Pat Guest",
"customer_email": "pat@example.com"
}'
/api/v1/partner/bookings/:id/cancel
Cancel a booking
Cancels a booking by id.
Locations, scheduling & orders
/api/v1/partner/locations
List locations
Locations with address and range scheduling config (slot/turnover minutes, advance window).
/api/v1/partner/locations/:id
Retrieve a location
A single location by id.
/api/v1/partner/events
List events
Events. Filters: status, visibility, updated_since.
/api/v1/partner/events/:id
Retrieve an event
A single event by id.
/api/v1/partner/events/:id/roster
Event roster
Attendees and their check-in state. Query: event_instance_id to scope to one session.
/api/v1/partner/orders
List orders
Orders with line items (cents). Filters: status, channel, updated_since.
/api/v1/partner/orders/:id
Retrieve an order
A single order by id.
Events
Events represent scheduled sessions that users can register for. Each event has ticket types, dates, and an optional venue.
/api/v1/events
List events
Returns upcoming and past events, sorted by start time.
curl -X GET "https://your-domain.example.com/api/v1/events" \
-H "Authorization: Bearer YOUR_API_TOKEN"
{
"data": [
{
"id": 42,
"slug": "annual-retreat",
"title": "Annual Retreat",
"starts_at": "2026-06-15T09:00:00Z",
"ends_at": "2026-06-15T17:00:00Z",
"location": "123 Main St",
"ticket_price_cents": 9900
}
]
}
/api/v1/events/:slug
Retrieve an event
Fetch a single event by its slug, including ticket types and add-ons.
/api/v1/events/:slug/register
Register for an event
Registers the authenticated user for the given event.
curl -X POST "https://your-domain.example.com/api/v1/events/annual-retreat/register" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ticket_type_id": 7, "quantity": 1}'
Courses & Lessons
Courses contain sections and lessons. Once a user is enrolled, they can track lesson progress and complete the course.
/api/v1/courses
List courses
Returns all courses the authenticated user can see.
curl -X GET "https://your-domain.example.com/api/v1/courses" \
-H "Authorization: Bearer YOUR_API_TOKEN"
/api/v1/courses/:slug
Retrieve a course
Fetch a single course by slug, including its sections.
/api/v1/courses/:slug/enroll
Enroll in a course
Enrolls the authenticated user in the given course.
curl -X POST "https://your-domain.example.com/api/v1/courses/intro-workshop/enroll" \
-H "Authorization: Bearer YOUR_API_TOKEN"
/api/v1/lessons/:id/complete
Mark a lesson complete
Marks the given lesson as completed for the authenticated user.
Memberships
Memberships give users access to tiered benefits and, optionally, courses gated behind a membership level. Use these endpoints to check the current user's status.
/api/v1/membership
Current membership
Returns the authenticated user's active membership, if any.
curl -X GET "https://your-domain.example.com/api/v1/membership" \
-H "Authorization: Bearer YOUR_API_TOKEN"
{
"data": {
"id": "membership-uuid",
"level": "Gold",
"status": "active",
"current_period_end": "2026-12-31T23:59:59Z"
}
}
/api/v1/memberships
List membership history
Returns all past and present memberships for the authenticated user.
Groups
Groups let an organization buy seats in bulk and assign them to members (for example, a company training account). Group admins can invite new members, manage seats, and track course progress for their roster.
/api/v1/groups
List groups
Returns the groups the authenticated user belongs to or administers.
curl -X GET "https://your-domain.example.com/api/v1/groups" \
-H "Authorization: Bearer YOUR_API_TOKEN"
/api/v1/groups/:group_id/invitations
Invite a member
Sends an invitation to join the group to an email address.
/api/v1/groups/:group_id/seats
Seat usage
Shows how many seats are used and how many remain.
Community
The community API powers the social feed: posts, comments, likes, categories, and leaderboards. Posts are scoped to categories and respect block/report rules.
/api/v1/community/feed
Community feed
Returns a paginated feed of community posts.
curl -X GET "https://your-domain.example.com/api/v1/community/feed?page=1&per_page=20" \
-H "Authorization: Bearer YOUR_API_TOKEN"
/api/v1/community/posts
Create a post
Creates a new community post in the specified category.
/api/v1/community/posts/:id/like
Like or unlike a post
Toggles the authenticated user's like on the given post.
Contacts (CRM)
The contacts API is the CRM layer: create, update, tag, and note contacts; move them in and out of lists; and read their activity history.
/api/v1/contacts
List contacts
Returns a paginated list of contacts.
/api/v1/contacts
Create a contact
Creates a new contact. Email is required; other fields are optional.
curl -X POST "https://your-domain.example.com/api/v1/contacts" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+15551234567"
}'
/api/v1/contacts/:id/tags
Add tags to a contact
Attaches one or more tags to the given contact.
/api/v1/contacts/:contact_id/notes
List contact notes
Returns notes attached to the given contact, newest first.
Push Notifications
The push notifications API lets your mobile app register device tokens and send notifications to users. Device tokens are bound to the currently authenticated user and can be registered for either iOS (APNs) or Android (FCM).
/api/v1/devices
Register a device token
Stores a device token for the authenticated user so they can receive push notifications.
curl -X POST "https://your-domain.example.com/api/v1/devices" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_token": "APNS_OR_FCM_DEVICE_TOKEN",
"platform": "ios",
"environment": "production",
"bundle_id": "com.example.yourapp"
}'
{
"data": {
"id": "device-uuid",
"platform": "ios",
"environment": "production",
"active": true
}
}
/api/v1/devices
Deactivate a device token
Marks a previously registered device token inactive so it stops receiving notifications.
/api/v1/push/devices
List your devices
Returns the authenticated user's registered device tokens. Tokens are masked in the response.
/api/v1/push/test
Send a test notification
Sends a test push to every device registered to the authenticated user.
curl -X POST "https://your-domain.example.com/api/v1/push/test" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Hello from the API",
"body": "This is a test notification."
}'
/api/v1/push/send
Send a notification to a user
Sends a push notification to a specific user by email. Requires an owner, admin, or instructor role.
curl -X POST "https://your-domain.example.com/api/v1/push/send" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"title": "Your order is ready",
"body": "Stop by when you get a chance.",
"data": {"type": "order_update", "order_id": "ORDER-12345"}
}'
environment
field on a registered device must match the APNs environment your build
is signed for:
sandbox
for TestFlight and development builds,
production
for App Store releases. A mismatch will silently drop
the notification.
Live Activities
Live Activities are the iOS feature that shows real-time updates on the
lock screen and in the Dynamic Island. The API lets you start an activity
for a user, advance it through named steps, and end it when the work is
done. Each activity is described as an ordered list of
steps
(for example, ["Received", "Processing", "Shipped", "Delivered"]).
Two authentication styles are supported: your mobile app uses a Bearer
token to register update tokens for a running activity, while external
callers (automations, webhooks, CRMs) use an
X-Admin-Key
header to start, advance, and end activities by user email.
Mobile app endpoints
/api/v1/live-activities/push-to-start/register
Register a push-to-start token
Stores the push-to-start token so the server can wake the device and start a new activity.
/api/v1/live-activities/register
Register an activity update token
Stores the per-activity push token that iOS issues after the activity is live, so the server can send incremental updates.
curl -X POST "https://your-domain.example.com/api/v1/live-activities/register" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"activity_id": "activity-uuid",
"reference_id": "ORDER-12345",
"push_token": "IOS_ACTIVITY_PUSH_TOKEN",
"environment": "production"
}'
External caller endpoints
/api/v1/push/live-activity/start
Start a Live Activity
Starts a new Live Activity for a user identified by email. Returns the activity id that you will use for subsequent advance and end calls.
curl -X POST "https://your-domain.example.com/api/v1/push/live-activity/start" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"activity_type": "order_processing",
"reference_id": "ORDER-12345",
"title": "Order #12345",
"subtitle": "Tracking your order",
"steps": ["Received", "Processing", "Shipped", "Delivered"]
}'
{
"success": true,
"data": {
"id": "activity-uuid",
"activity_type": "order_processing",
"reference_id": "ORDER-12345",
"title": "Order #12345",
"subtitle": "Tracking your order",
"steps": ["Received", "Processing", "Shipped", "Delivered"],
"current_step_index": 0,
"current_step_name": "Received",
"total_steps": 4,
"status": "active",
"created_at": "2026-06-15T09:00:00Z",
"updated_at": "2026-06-15T09:00:00Z"
}
}
/api/v1/push/live-activity/:id/advance
Advance to the next step
Advances the activity to the next step in its steps array. Accepts an optional reference_id body parameter as an alternative to the path id.
curl -X POST "https://your-domain.example.com/api/v1/push/live-activity/activity-uuid/advance" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{}'
/api/v1/push/live-activity/:id/end
End a Live Activity
Ends the activity and stops future updates. Accepts an optional reference_id body parameter as an alternative to the path id, plus an optional status (completed, cancelled, or failed).
curl -X POST "https://your-domain.example.com/api/v1/push/live-activity/activity-uuid/end" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "completed"}'
/api/v1/push/live-activities
List Live Activities
Returns all Live Activities. Filter with the status and activity_type query parameters.
/api/v1/push/live-activities/:id
Retrieve a Live Activity
Fetches a single Live Activity by id, including its current step and status.
reference_id)
rather than trying to resurrect a completed one.
Approvals
Approvals let automation steps pause and wait for a human to approve or deny
them before continuing. Two authentication styles are supported: your mobile
app uses a Bearer token to list and respond to approvals, while external
callers (automations, webhooks, CRMs) use an
X-Admin-Key
header to send interactive approval push notifications.
Mobile app endpoints
/api/v1/approvals
List approvals
Returns approvals assigned to the authenticated user. Use the status query parameter to filter by pending, approved, or denied.
curl -X GET "https://your-domain.example.com/api/v1/approvals?status=pending" \
-H "Authorization: Bearer YOUR_API_TOKEN"
{
"approvals": [
{
"id": "approval-uuid",
"status": "pending",
"requested_at": "2026-06-15T09:00:00Z",
"responded_at": null,
"expires_at": "2026-06-16T09:00:00Z",
"run_id": "run-uuid",
"step_id": "step-uuid",
"context": {
"summary": "Approve outbound message",
"details": "This automation step requires human review before continuing."
}
}
]
}
/api/v1/approvals/:id/approve
Approve a request
Approves the approval request. The paused automation run resumes on the next scheduler tick.
curl -X POST "https://your-domain.example.com/api/v1/approvals/approval-uuid/approve" \
-H "Authorization: Bearer YOUR_API_TOKEN"
/api/v1/approvals/:id/deny
Deny a request
Denies the approval request and signals the automation run to branch accordingly.
External caller endpoints
/api/v1/admin/push/send-approval
Send an interactive approval notification
Sends a rich push notification to a user by email with inline approve/reject action buttons. The iOS app renders the actions via the Notification Service Extension and persists them to the widget store. Authenticated via X-Admin-Key header.
For simple cases, pass convenience parameters and the endpoint builds the
action buttons for you. For full control, pass a custom
approval_actions
array instead.
| Parameter | Required | Description |
|---|---|---|
| Yes | Email of the user to notify. | |
| title | No | Notification title. Defaults to "Approval Required". |
| body | No | Notification body text. |
| subtitle | No | Secondary text shown in the approval card. |
| details | No | Additional context rendered in the expanded notification view. |
| approval_id | No | Your own ID for this approval. A UUID is generated if omitted. Used to build the default approve/reject URLs. |
| approve_url | No |
Override the approve action endpoint. Defaults to /api/v1/approvals/:id/approve.
|
| reject_url | No |
Override the reject action endpoint. Defaults to /api/v1/approvals/:id/deny.
|
| approve_label | No | Label for the approve button. Defaults to "Approve". |
| reject_label | No | Label for the reject button. Defaults to "Reject". |
| auth_type | No | Authentication method the app uses when calling action endpoints. Defaults to "app_token". |
| approval_mode | No | How the approval is rendered. Defaults to "direct". |
| plugin_name | No | Source system name shown in the notification. Defaults to "PewPros". |
| expires_at | No | ISO 8601 timestamp after which the approval is no longer actionable. |
| approval_actions | No | Full custom actions array. When provided, all convenience params (approve_url, reject_url, labels, auth_type) are ignored. |
curl -X POST "https://your-domain.example.com/api/v1/admin/push/send-approval" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"title": "New Instructor Application",
"body": "John Smith has applied to become an instructor.",
"subtitle": "Review and approve or reject this application.",
"approval_id": "instr-app-42",
"approve_label": "Accept",
"reject_label": "Decline"
}'
{
"success": true,
"sent_to": "user@example.com",
"result": {
"total": 1,
"successful": 1,
"failed": 0
},
"data": {
"type": "interactive_approval",
"notification_type": "approval",
"id": "approval_instr-app-42",
"plugin_name": "PewPros",
"category": "approval",
"approval_required": true,
"approval_mode": "direct",
"item_title": "New Instructor Application",
"item_subtitle": "Review and approve or reject this application.",
"item_details": "",
"status": "pending",
"timestamp": "2026-06-15T09:00:00Z",
"approval_actions": [
{
"action_id": "approve",
"label": "Accept",
"api_endpoint": "https://your-domain.example.com/api/v1/approvals/instr-app-42/approve",
"method": "POST",
"confirm": false,
"auth_type": "app_token"
},
{
"action_id": "reject",
"label": "Decline",
"api_endpoint": "https://your-domain.example.com/api/v1/approvals/instr-app-42/deny",
"method": "POST",
"confirm": true,
"auth_type": "app_token"
}
]
}
}
curl -X POST "https://your-domain.example.com/api/v1/admin/push/send-approval" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"title": "Approve Payout",
"body": "A $500.00 payout is ready to send.",
"approval_id": "payout-99",
"approval_actions": [
{
"action_id": "approve",
"label": "Send Payout",
"api_endpoint": "https://hooks.example.com/payout/99/approve",
"method": "POST",
"confirm": false,
"auth_type": "webhook_hmac"
},
{
"action_id": "reject",
"label": "Hold",
"api_endpoint": "https://hooks.example.com/payout/99/hold",
"method": "POST",
"confirm": true,
"auth_type": "webhook_hmac"
}
]
}'
expires_at
passes, the approval is no longer actionable and the automation run will
follow its configured timeout branch.
Automations
Automations are event-driven workflows that run automatically when a trigger fires. You can build automations visually in the admin panel or create them via the MCP tools. Each automation has a trigger type, an optional trigger config for filtering, and a definition containing the workflow nodes and edges.
Supported trigger types include membership_created, course_enrolled, event_booked, payment_received, webhook_received, schedule,
and more. Automations must be activated before they respond to events.
Workflow nodes represent individual steps such as sending an email, adding a
CRM tag, creating a delay, or branching based on a condition. The visual builder
at
/admin/automations/:id/build
lets you drag, connect, and configure nodes.
Automation lifecycle
| Status | Description |
|---|---|
| draft | Newly created. Will not respond to trigger events. |
| active | Listening for trigger events and creating runs automatically. |
| paused | Temporarily stopped. Existing runs complete but no new runs are created. |
| archived | Retired. Hidden from the active list. |
Approval gates
Any automation node can be configured to require approval before executing.
When a run reaches an approval node, it pauses and sends a push notification
to the admin. The run resumes when the approval is granted via the mobile app,
the web UI, or the
/api/v1/approvals/:id/approve
endpoint (see the
Approvals
section).
Webhook Triggers
Automations with trigger type
webhook_received
get a unique, unguessable webhook URL. Any external system can fire the
automation by sending a POST request to that URL. The URL uses a UUID path
segment that is auto-generated when the automation is created.
/webhooks/automation/:uuid
Fire a webhook automation
Sends a JSON payload to trigger all active automations matching this webhook path. Returns a correlation ID for tracing.
curl -X POST "https://your-domain.example.com/webhooks/automation/dd132cc3-824e-4047-8010-7c6154e6f69b" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"event_type": "signup",
"name": "Jane Doe"
}'
{
"status": "ok",
"path": "dd132cc3-824e-4047-8010-7c6154e6f69b",
"correlation_id": "a3bbeddb-5c15-46ab-8df7-a3415cf69def"
}
Finding the webhook URL
The webhook URL is displayed in three places in the admin panel:
- The automation Show page (with a Copy button and sample cURL)
- The Edit form (read-only field with Copy button)
- The Builder header bar
It is also returned by the MCP tools automation_get, automation_create, and
automation_list
in the
webhook_url
field.
Payload mappings
By default, the incoming JSON body is nested under
trigger.body
in the automation context. Payload mappings let you extract nested fields and
promote them to top-level trigger variables so they can be referenced as
{{trigger.user_email}}
in downstream nodes.
Each mapping has a name
(the variable name to create) and a source
(a dot-path into the trigger data). For example,
body.email
extracts the
email
field from the webhook body.
{
"trigger_config": {
"path": "dd132cc3-824e-4047-8010-7c6154e6f69b",
"payload_mappings": [
{"name": "user_email", "source": "body.email"},
{"name": "action", "source": "body.event_type"},
{"name": "user_name", "source": "body.name"}
]
}
}
With the mappings above, a POST with
email
in the body produces this trigger data:
{
"path": "dd132cc3-...",
"body": {
"email": "user@example.com",
"event_type": "signup",
"name": "Jane Doe"
},
"headers": {
"content-type": "application/json",
"x-custom-header": "value"
},
"received_at": "2026-04-06T04:13:55Z",
"correlation_id": "a3bbeddb-...",
"user_email": "user@example.com",
"action": "signup"
}
Headers
All x-*
prefixed headers, plus
content-type
and user-agent,
are captured in trigger.headers.
You can map header values with a source path like headers.x-api-key.
Deduplication
Duplicate payloads (identical JSON body within a short window) are automatically
detected and return a
"status": "deduplicated"
response. The automation does not fire a second time. Each delivery is logged
with a correlation ID for debugging.
MCP Tools
The platform exposes an
MCP (Model Context Protocol)
server that lets AI agents interact with every feature programmatically. The
MCP endpoint is at
/mcp/v1
and uses the same Bearer token as the REST API.
POST /mcp/v1 HTTP/1.1
Host: your-domain.example.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Available tool categories
| Category | Tools | Description |
|---|---|---|
| CRM | 14 | Contacts, tags, lists, notes, activities |
| Automation | 15 | Create, list, trigger, manage runs, approvals, templates, node types |
| Events | 6 | List, get, register, update capacity, cancel |
| Courses | 3 | List, get details, enroll students |
| Messaging | 3 | Conversations, messages, send SMS |
| Agency | 5 | Multi-tenant provisioning, snapshots, locations |
| Other | 15 | Groups, community, appointments, certifications, shop, reporting |
Automation MCP tools
The automation tools let AI agents create and manage workflows programmatically.
Webhook automations return a
webhook_url
field in all responses.
| Tool | Description |
|---|---|
| automation_list | List automations with filters |
| automation_get | Get full automation with definition |
| automation_create | Create from scratch or from template |
| automation_update | Update name, trigger, or definition |
| automation_delete | Delete an automation |
| automation_activate | Set status to active |
| automation_deactivate | Set status to paused |
| automation_trigger | Manually trigger a run |
| automation_list_runs | List runs with status filter |
| automation_get_run | Get run details with all steps |
| automation_cancel_run | Cancel a running automation |
| automation_list_templates | Browse pre-built templates |
| automation_list_node_types | List available node types |
| automation_list_approvals | List pending approval requests |
| automation_respond_approval | Approve or deny an approval |
Errors
Errors follow a consistent JSON shape. The HTTP status code indicates the category of failure; the body provides additional detail.
{
"errors": [
{
"message": "Resource not found",
"code": "not_found"
}
]
}
| Status | Meaning |
|---|---|
| 200 | OK. The request succeeded. |
| 201 | Created. The resource was created successfully. |
| 400 | Bad request. The request body or parameters were invalid. |
| 401 | Unauthorized. Missing or invalid Bearer token. |
| 403 | Forbidden. Your token does not have access to this resource. |
| 404 | Not found. The resource does not exist. |
| 422 | Unprocessable. Validation failed; see the errors array. |
| 429 | Too many requests. Slow down and retry after a short delay. |
| 500 | Server error. Please try again later. |
Next steps
Ready to build? Head to the interactive Swagger UI to browse every endpoint and try calls against your own account. If you'd rather work locally, download the spec from /api/docs/openapi.json and import it into your API client or code generator.
We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect