Skip to main content

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.

Example request

            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.

401 Missing or invalid token

            {
  "error": "unauthorized"
}

          
A handful of endpoints (for example, 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.

Typical POST request

            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.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/partner/customers" \
  -H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"

          
200 Lean customer payload with tier + waiver

            {
  "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}
}

          
A missing or invalid key returns 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.

Incremental sync (cursor pattern)

            # 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 sync recipe (changed-only, paginated)

            # 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.

          
This is offset pagination, so do not change the cursor mid-walk — keep 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

GET /api/v1/partner/customers

List customers

Lean PII plus the customer's discount tier and waiver state inline. Supports search and updated_since.

GET /api/v1/partner/customers/:id

Retrieve a customer

A single customer by id.

GET /api/v1/partner/membership-levels

List membership tiers

All membership levels/tiers (id, name, slug, price_cents) to map to your POS discount rules.

GET /api/v1/partner/membership-levels/:id

Retrieve a membership tier

A single membership level.

GET /api/v1/partner/memberships

List memberships

Memberships with member identity, status, and tier block. Filter by status or membership_level_id.

GET /api/v1/partner/memberships/:id

Retrieve a membership

A single membership by id.

Range lanes & reservations

GET /api/v1/partner/range/lanes

List lanes

Active range lanes. Filter by location_id or active.

GET /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).

Check availability

            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"

          
200 Open start times

            {
  "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"
    ]
  }
}

          
GET /api/v1/partner/range/reservations

List reservations

Reservations. Filters: lane_id, location_id, status, from, to, updated_since.

GET /api/v1/partner/range/reservations/:id

Retrieve a reservation

A single reservation by id.

POST /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.

Create a reservation

            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
  }'

          
201 Reservation created (server-priced)

            {
  "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"
  }
}

          
POST /api/v1/partner/range/reservations/:id/cancel

Cancel a reservation

Cancels a reservation by id.

Cancel a reservation

            curl -X POST "https://your-domain.example.com/api/v1/partner/range/reservations/6/cancel" \
  -H "X-Partner-Key: sk_partner_YOUR_SECRET_KEY"

          
Partner bookings have no platform user, so they are guest bookings and are priced at walk-up rates server-side — never send a price. They default to 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

GET /api/v1/partner/resources

List resources

Bookable resources (bays, rooms, simulators). Filter by location_id or active.

GET /api/v1/partner/resources/:id

Retrieve a resource

A single resource by id.

GET /api/v1/partner/resources/:id/availability

Resource availability

Capacity-aware slots. Query: date (required), duration, party_size.

GET /api/v1/partner/bookings

List bookings

Resource bookings. Filters: status, from, to, updated_since.

GET /api/v1/partner/bookings/:id

Retrieve a booking

A single booking by id.

POST /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.

Create a booking

            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"
  }'

          
POST /api/v1/partner/bookings/:id/cancel

Cancel a booking

Cancels a booking by id.

Locations, scheduling & orders

GET /api/v1/partner/locations

List locations

Locations with address and range scheduling config (slot/turnover minutes, advance window).

GET /api/v1/partner/locations/:id

Retrieve a location

A single location by id.

GET /api/v1/partner/events

List events

Events. Filters: status, visibility, updated_since.

GET /api/v1/partner/events/:id

Retrieve an event

A single event by id.

GET /api/v1/partner/events/:id/roster

Event roster

Attendees and their check-in state. Query: event_instance_id to scope to one session.

GET /api/v1/partner/orders

List orders

Orders with line items (cents). Filters: status, channel, updated_since.

GET /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.

GET /api/v1/events

List events

Returns upcoming and past events, sorted by start time.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/events" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
200 List of events

            {
  "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
    }
  ]
}

          
GET /api/v1/events/:slug

Retrieve an event

Fetch a single event by its slug, including ticket types and add-ons.

POST /api/v1/events/:slug/register

Register for an event

Registers the authenticated user for the given event.

Example request

            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.

GET /api/v1/courses

List courses

Returns all courses the authenticated user can see.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/courses" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
GET /api/v1/courses/:slug

Retrieve a course

Fetch a single course by slug, including its sections.

POST /api/v1/courses/:slug/enroll

Enroll in a course

Enrolls the authenticated user in the given course.

Example request

            curl -X POST "https://your-domain.example.com/api/v1/courses/intro-workshop/enroll" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
POST /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.

GET /api/v1/membership

Current membership

Returns the authenticated user's active membership, if any.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/membership" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
200 Active membership

            {
  "data": {
    "id": "membership-uuid",
    "level": "Gold",
    "status": "active",
    "current_period_end": "2026-12-31T23:59:59Z"
  }
}

          
GET /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.

GET /api/v1/groups

List groups

Returns the groups the authenticated user belongs to or administers.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/groups" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
POST /api/v1/groups/:group_id/invitations

Invite a member

Sends an invitation to join the group to an email address.

GET /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.

GET /api/v1/community/feed

Community feed

Returns a paginated feed of community posts.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/community/feed?page=1&per_page=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
POST /api/v1/community/posts

Create a post

Creates a new community post in the specified category.

POST /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.

GET /api/v1/contacts

List contacts

Returns a paginated list of contacts.

POST /api/v1/contacts

Create a contact

Creates a new contact. Email is required; other fields are optional.

Example request

            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"
  }'

          
POST /api/v1/contacts/:id/tags

Add tags to a contact

Attaches one or more tags to the given contact.

GET /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).

POST /api/v1/devices

Register a device token

Stores a device token for the authenticated user so they can receive push notifications.

Example request

            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"
  }'

          
201 Device registered

            {
  "data": {
    "id": "device-uuid",
    "platform": "ios",
    "environment": "production",
    "active": true
  }
}

          
DELETE /api/v1/devices

Deactivate a device token

Marks a previously registered device token inactive so it stops receiving notifications.

GET /api/v1/push/devices

List your devices

Returns the authenticated user's registered device tokens. Tokens are masked in the response.

POST /api/v1/push/test

Send a test notification

Sends a test push to every device registered to the authenticated user.

Example request

            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."
  }'

          
POST /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.

Example request

            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"}
  }'

          
The 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

POST /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.

POST /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.

Example request

            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

POST /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.

Example request

            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"]
  }'

          
201 Activity started

            {
  "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"
  }
}

          
POST /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.

Example request

            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 '{}'

          
POST /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).

Example request

            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"}'

          
GET /api/v1/push/live-activities

List Live Activities

Returns all Live Activities. Filter with the status and activity_type query parameters.

GET /api/v1/push/live-activities/:id

Retrieve a Live Activity

Fetches a single Live Activity by id, including its current step and status.

Once an activity is ended, iOS will not accept further updates for it. Always start a new activity (with a fresh 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

GET /api/v1/approvals

List approvals

Returns approvals assigned to the authenticated user. Use the status query parameter to filter by pending, approved, or denied.

Example request

            curl -X GET "https://your-domain.example.com/api/v1/approvals?status=pending" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
200 Pending approvals

            {
  "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."
      }
    }
  ]
}

          
POST /api/v1/approvals/:id/approve

Approve a request

Approves the approval request. The paused automation run resumes on the next scheduler tick.

Example request

            curl -X POST "https://your-domain.example.com/api/v1/approvals/approval-uuid/approve" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

          
POST /api/v1/approvals/:id/deny

Deny a request

Denies the approval request and signals the automation run to branch accordingly.

External caller endpoints

POST /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
email 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.
Convenience params example

            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"
  }'

          
200 Notification sent

            {
  "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"
      }
    ]
  }
}

          
Full custom actions example

            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"
      }
    ]
  }'

          
Approvals can expire. Once 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.

POST /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.

Example request

            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"
  }'

          
200 Webhook accepted

            {
  "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 with mappings

            {
  "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:

Resulting 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.

Webhook URLs are secret. Treat them like API keys: do not expose them in client-side code or public repositories. If a URL is compromised, edit the automation and the system will preserve the existing UUID path, or you can delete and recreate the automation to get a new URL.

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.

MCP connection

            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
For the full parameter reference for each MCP tool, see the Interactive API docs or the full MCP specification .

Errors

Errors follow a consistent JSON shape. The HTTP status code indicates the category of failure; the body provides additional detail.

404 Resource not found

            {
  "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.