Introduction
LoopSpark Public API
This documentation aims to provide all the information you need to work with our API.
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_FRANCHISE_TOKEN}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
To obtain a token, please contact our support at support@loopspark.com.
Authorization
To authorize your requests, you need to pass a token in the request header Authorization.
Retrieving your franchise
requires authentication
This endpoint should be used to make sure that the your authorization token is valid. It will return an object with the name of the franchise.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/me" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/me"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/me';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"name": "Your franchise name"
},
"meta": {
"success": true
}
}
Example response (403):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Integrations - MindBODY
Get the authorization link from MINDBODY.
requires authentication
This is the first step required in order to activate a SiteID.
Confirm that the authorization is completed
requires authentication
This is the second and final step to activate a SiteID. After the authorization link is created (using GET authorization-link), proceed to activate the SiteID on MINDBODY. Once it's active, confirm it to LoopSpark by using this POST request. The SiteID needs to have Locations already available on MINDBODY (beside the "Online Store")
Automation
All the Endpoints regarding Automations of your franchise
Automations list
requires authentication
Retrieve a list of all the Automations available. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/automations?replicas_of_id=123&include_paused=1&include_archived=1&only_primaries=1&per_page=10&only_type=SMS&sort_by=id&sort_direction=asc&site_id=12345&category=Welcome+Sequence" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/automations"
);
const params = {
"replicas_of_id": "123",
"include_paused": "1",
"include_archived": "1",
"only_primaries": "1",
"per_page": "10",
"only_type": "SMS",
"sort_by": "id",
"sort_direction": "asc",
"site_id": "12345",
"category": "Welcome Sequence",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/automations';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'replicas_of_id' => '123',
'include_paused' => '1',
'include_archived' => '1',
'only_primaries' => '1',
'per_page' => '10',
'only_type' => 'SMS',
'sort_by' => 'id',
'sort_direction' => 'asc',
'site_id' => '12345',
'category' => 'Welcome Sequence',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": 31,
"team_id": "6316",
"active": false,
"archived": false,
"disabled": false,
"is_primary": false,
"replica_of": null,
"message": {
"subject": "eius et animi",
"content": "Et fugiat sunt nihil accusantium. Mollitia modi deserunt aut ab provident perspiciatis quo. Nostrum aut adipisci quidem nostrum.\n\nIncidunt iure odit et et modi ipsum. Omnis autem et consequatur.\n\nEnim non facere tempora ex voluptatem laboriosam praesentium. Adipisci molestias fugit deleniti distinctio eum doloremque id. Libero aliquam veniam corporis dolorem mollitia deleniti."
},
"settings": {
"name": "architecto",
"category": null,
"delivery_type": "SMS",
"one_off": false,
"send_window": {
"immediate": true,
"start_mins": null,
"end_mins": null
},
"start_at": null
},
"audience": "sint aut molestiae",
"created_at": "2026-10-03T08:00:35+00:00",
"updated_at": "2026-10-03T08:00:35+00:00",
"last_ran_at": null,
"tags": {
"tags": [],
"add": 1
},
"todo": {
"overdue_hours": 0,
"postponed_hours": null,
"priority": "LOW",
"tags": []
},
"stats": {
"bounced": 0,
"clicked": 0,
"delivered": 0,
"opened": 0,
"sent": 0,
"unsubscribe_clicked": 0
}
},
{
"id": 32,
"team_id": "8222",
"active": false,
"archived": false,
"disabled": false,
"is_primary": false,
"replica_of": null,
"message": {
"subject": "suscipit doloribus fugiat",
"content": "Deserunt et error neque recusandae et. Dolorem et ut dicta. Assumenda consequatur ut et sunt quisquam. Repellendus ut eaque alias ratione dolores.\n\nEa ut aut deserunt sint quis. Quod id aspernatur consectetur id a consectetur assumenda. Neque sit sunt nihil accusantium odit ut perspiciatis.\n\nAut quis ut dolores omnis et earum. Asperiores est vel id aut officiis eos. Et qui unde et."
},
"settings": {
"name": "sunt",
"category": null,
"delivery_type": "SMS",
"one_off": false,
"send_window": {
"immediate": true,
"start_mins": null,
"end_mins": null
},
"start_at": null
},
"audience": "sed quasi et",
"created_at": "2026-10-03T08:00:35+00:00",
"updated_at": "2026-10-03T08:00:35+00:00",
"last_ran_at": null,
"tags": {
"tags": [],
"add": 1
},
"todo": {
"overdue_hours": 0,
"postponed_hours": null,
"priority": "LOW",
"tags": []
},
"stats": {
"bounced": 0,
"clicked": 0,
"delivered": 0,
"opened": 0,
"sent": 0,
"unsubscribe_clicked": 0
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
stats
object
This field includes the all-time statistics for the automation. The format changes depending on the type of automation.
Single Automation
requires authentication
Show a single Automation by ID
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/automations/123" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/automations/123"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/automations/123';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": 33,
"team_id": "2131",
"active": false,
"archived": false,
"disabled": false,
"is_primary": false,
"replica_of": null,
"message": {
"subject": "adipisci quidem nostrum",
"content": "Incidunt iure odit et et modi ipsum. Omnis autem et consequatur.\n\nEnim non facere tempora ex voluptatem laboriosam praesentium. Adipisci molestias fugit deleniti distinctio eum doloremque id. Libero aliquam veniam corporis dolorem mollitia deleniti.\n\nQuia officia est dignissimos neque. Odio veritatis excepturi doloribus delectus fugit qui repudiandae. Est alias tenetur ratione. Voluptate accusamus ut et recusandae."
},
"settings": {
"name": "aut",
"category": null,
"delivery_type": "TODO",
"one_off": false,
"send_window": {
"immediate": true,
"start_mins": null,
"end_mins": null
},
"start_at": null
},
"audience": "eaque alias ratione",
"created_at": "2026-10-03T08:00:35+00:00",
"updated_at": "2026-10-03T08:00:35+00:00",
"last_ran_at": null,
"tags": {
"tags": [],
"add": 1
},
"todo": {
"overdue_hours": 0,
"postponed_hours": null,
"priority": "LOW",
"tags": []
},
"stats": {
"completed": 0,
"created": 0,
"deleted": 0,
"open": 0,
"postponed": 0
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update an Automation
requires authentication
Update an Automation by ID
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/automations/1234" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"active\": true,
\"archived\": true,
\"name\": \"bngzmiyvdljnikhw\",
\"category_name\": \"aykcmyuwpwlvqwrs\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/automations/1234"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"active": true,
"archived": true,
"name": "bngzmiyvdljnikhw",
"category_name": "aykcmyuwpwlvqwrs"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/automations/1234';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'active' => true,
'archived' => true,
'name' => 'bngzmiyvdljnikhw',
'category_name' => 'aykcmyuwpwlvqwrs',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": 34,
"team_id": "4680",
"active": false,
"archived": false,
"disabled": false,
"is_primary": false,
"replica_of": null,
"message": {
"subject": null,
"content": null
},
"settings": {
"name": "et",
"category": null,
"delivery_type": "TAG",
"one_off": false,
"send_window": {
"immediate": true,
"start_mins": null,
"end_mins": null
},
"start_at": null
},
"audience": "consectetur assumenda eaque",
"created_at": "2026-10-03T08:00:35+00:00",
"updated_at": "2026-10-03T08:00:35+00:00",
"last_ran_at": null,
"tags": {
"tags": [
"ciao",
"second"
],
"add": 1
},
"todo": {
"overdue_hours": 0,
"postponed_hours": 0,
"priority": "LOW",
"tags": []
},
"stats": {
"created": 0
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Stats
requires authentication
Get the stats of a single automation
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/automations/123/stats?start_date=1581418800&end_date=1581678000" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/automations/123/stats"
);
const params = {
"start_date": "1581418800",
"end_date": "1581678000",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/automations/123/stats';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'start_date' => '1581418800',
'end_date' => '1581678000',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": 123,
"team_id": "foobar",
"delivery_type": "EMAIL",
"sent": 20,
"bounced": 1,
"delivered": 19,
"opened": 0,
"clicked": 0
}
}
Example response (200):
{
"data": {
"id": 123,
"team_id": "foobar",
"delivery_type": "SMS",
"sent": 20,
"bounced": 1,
"delivered": 19,
"opened": 0,
"clicked": 0
}
}
Example response (200):
{
"data": {
"id": 123,
"team_id": "foobar",
"delivery_type": "TODO",
"created": 100,
"completed": 25,
"open": 40,
"postponed": 35,
"deleted": 5
}
}
Example response (200):
{
"data": {
"id": 123,
"team_id": "foobar",
"delivery_type": "TAG",
"created": 100
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Action history
requires authentication
Show the most recent actions performed by an automation for a specific client.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/automations/123/actions/12345?start_date=1581418800&end_date=1581678000" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/automations/123/actions/12345"
);
const params = {
"start_date": "1581418800",
"end_date": "1581678000",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/automations/123/actions/12345';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'start_date' => '1581418800',
'end_date' => '1581678000',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"type": "EMAIL",
"action": {
"delivery_type": "EMAIL",
"sent": false,
"bounced": false,
"delivered": false,
"opened": false,
"clicked": false,
"last_contacted_at": "2026-10-03T08:00:35+00:00",
"closed": false
}
},
{
"type": "EMAIL",
"action": {
"delivery_type": "EMAIL",
"sent": false,
"bounced": false,
"delivered": false,
"opened": false,
"clicked": false,
"last_contacted_at": "2026-10-03T08:00:35+00:00",
"closed": false
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Staff
All the Endpoints regarding Staff members of your franchise
Staff list
requires authentication
Retrieve a list of all the staff members available. Staff members can either be already assigned to a Studio, or they can be the ones created via API. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/staff?per_page=10&sort_by=email&sort_direction=asc&site_id=12345" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/staff"
);
const params = {
"per_page": "10",
"sort_by": "email",
"sort_direction": "asc",
"site_id": "12345",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/staff';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'sort_by' => 'email',
'sort_direction' => 'asc',
'site_id' => '12345',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"name": "Ms. Audra Crooks II",
"email": "gulgowski.asia@example.com",
"phone": null
},
{
"name": "Jermaine Tillman",
"email": "mya96@example.com",
"phone": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a Staff
requires authentication
Create a new staff member
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/staff" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"email\": \"gbailey@example.net\",
\"name\": \"miyvdljnikhwaykcmyuwpwlvqwr\",
\"phone\": \"architecto\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/staff"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "gbailey@example.net",
"name": "miyvdljnikhwaykcmyuwpwlvqwr",
"phone": "architecto"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/staff';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'email' => 'gbailey@example.net',
'name' => 'miyvdljnikhwaykcmyuwpwlvqwr',
'phone' => 'architecto',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"name": "Ms. Elisabeth Okuneva",
"email": "idickens@example.org",
"phone": null,
"studios": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get Staff detail
requires authentication
Shows the details of a specific Staff member, including all the Studios they are part of and their role
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/staff/foo@bar.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/staff/foo@bar.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/staff/foo@bar.com';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"name": "Morgan Hirthe",
"email": "dare.emelie@example.com",
"phone": null,
"studios": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Detach a Staff
requires authentication
Detach a staff member from every Studio
Example request:
curl --request DELETE \
"https://loopspark.com/api/franchise/v1/staff/foo@bar.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/staff/foo@bar.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/staff/foo@bar.com';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"name": "Morgan Hirthe",
"email": "imclaughlin@example.org",
"phone": null,
"studios": []
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a Staff to a Studio
requires authentication
Add a Staff member to a specific Studio, with a defined role.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/staff" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"email\": \"gbailey@example.net\",
\"role\": \"owner\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/staff"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "gbailey@example.net",
"role": "owner"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/staff';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'email' => 'gbailey@example.net',
'role' => 'owner',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "foobar",
"name": "My Yoga Studio",
"phone_number": "12225551234",
"api_set": true,
"website": "https://mystudioisgreat.com/landing",
"timezone": "UTC",
"start_date": "2020-09-30",
"creation_date": "2018-02-22T13:00:00+00:00",
"first_import_done": true,
"outgoing_settings": {
"email": {
"signature": "et",
"sender": "umonahan@hotmail.com",
"sender_name": "Prof. Nels Quitzon"
},
"phone": {
"sender_name": "16273854741",
"phone_forward": "12125558888"
},
"address": "4800 Oak Grove Dr, Pasadena, CA 91109"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"price": "$10",
"name": "Great Package",
"discount_code": "CouponName",
"discount_price": "20 dollars"
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"staff": [
{
"name": "Krystal Harvey",
"email": "alexandrea87@example.com",
"role": "owner"
}
],
"groups": [
{
"id": 1,
"name": "front-desk",
"staff": [
{
"name": "Katarina Funk",
"email": "john42@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "teachers",
"staff": []
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Change the Staff role
requires authentication
Change the role of a Staff member of a specific Studio. If the Staff member is not currently part of the Studio, it will be added to it.
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"role\": \"manager\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"role": "manager"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'role' => 'manager',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "foobar",
"name": "My Yoga Studio",
"phone_number": "12225551234",
"api_set": true,
"website": "https://mystudioisgreat.com/landing",
"timezone": "UTC",
"start_date": "2020-09-30",
"creation_date": "2018-02-22T13:00:00+00:00",
"first_import_done": true,
"outgoing_settings": {
"email": {
"signature": "et",
"sender": "umonahan@hotmail.com",
"sender_name": "Prof. Nels Quitzon"
},
"phone": {
"sender_name": "16273854741",
"phone_forward": "12125558888"
},
"address": "4800 Oak Grove Dr, Pasadena, CA 91109"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"price": "$10",
"name": "Great Package",
"discount_code": "CouponName",
"discount_price": "20 dollars"
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"staff": [
{
"name": "Krystal Harvey",
"email": "alexandrea87@example.com",
"role": "owner"
}
],
"groups": [
{
"id": 1,
"name": "front-desk",
"staff": [
{
"name": "Katarina Funk",
"email": "john42@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "teachers",
"staff": []
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Detach a Staff from a Studio
requires authentication
Removes a Staff member from a Studio
Example request:
curl --request DELETE \
"https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/staff/foo@bar.com';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "foobar",
"name": "My Yoga Studio",
"phone_number": "12225551234",
"api_set": true,
"website": "https://mystudioisgreat.com/landing",
"timezone": "UTC",
"start_date": "2020-09-30",
"creation_date": "2018-02-22T13:00:00+00:00",
"first_import_done": true,
"outgoing_settings": {
"email": {
"signature": "et",
"sender": "umonahan@hotmail.com",
"sender_name": "Prof. Nels Quitzon"
},
"phone": {
"sender_name": "16273854741",
"phone_forward": "12125558888"
},
"address": "4800 Oak Grove Dr, Pasadena, CA 91109"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"price": "$10",
"name": "Great Package",
"discount_code": "CouponName",
"discount_price": "20 dollars"
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"staff": [
{
"name": "Krystal Harvey",
"email": "alexandrea87@example.com",
"role": "owner"
}
],
"groups": [
{
"id": 1,
"name": "front-desk",
"staff": [
{
"name": "Katarina Funk",
"email": "john42@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "teachers",
"staff": []
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Prospect
All the Endpoints regarding the prospects of a specific studio.
Prospect list
requires authentication
Retrieve a list of the prospects for a specific Studio. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/123/prospects?per_page=10&page=1&sort_by=id&sort_direction=asc&email=&mobile_phone=&opted_out_emails=1&opted_out_sms=1&tags[]=signed-up" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/123/prospects"
);
const params = {
"per_page": "10",
"page": "1",
"sort_by": "id",
"sort_direction": "asc",
"email": "",
"mobile_phone": "",
"opted_out_emails": "1",
"opted_out_sms": "1",
"tags[0]": "signed-up",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/123/prospects';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'page' => '1',
'sort_by' => 'id',
'sort_direction' => 'asc',
'email' => '',
'mobile_phone' => '',
'opted_out_emails' => '1',
'opted_out_sms' => '1',
'tags[0]' => 'signed-up',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "lead-rosario67@hotmail.com",
"profile_url": "https://loopspark.com/go/client/117/lead-rosario67@hotmail.com",
"email": "rosario67@hotmail.com",
"first_name": "Kayden",
"last_name": "Robel",
"mobile_phone": "13572103900",
"creation_date": "2021-02-03T11:32:02+00:00",
"referred_by": null,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true
},
{
"id": "lead-bryana.conn@hotmail.com",
"profile_url": "https://loopspark.com/go/client/3553/lead-bryana.conn@hotmail.com",
"email": "bryana.conn@hotmail.com",
"first_name": "Grover",
"last_name": "Oberbrunner",
"mobile_phone": "13577302692",
"creation_date": "1971-12-10T19:07:33+00:00",
"referred_by": null,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Add a Prospect
requires authentication
Adds a Prospect to the specified Studio.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/prospects" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"email\": \"gbailey@example.net\",
\"first_name\": \"miyvdljnikhwaykcmyuwpwlvqwr\",
\"last_name\": \"sitcpscqldzsnrwtujwvlxjklqp\",
\"referred_by\": \"architecto\",
\"tags\": [
\"ngzmiyvdljnikhwa\"
],
\"referrer\": {
\"first_name\": \"architecto\",
\"last_name\": \"architecto\",
\"email\": \"zbailey@example.net\",
\"phone\": \"architecto\",
\"location\": \"architecto\"
}
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/prospects"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"email": "gbailey@example.net",
"first_name": "miyvdljnikhwaykcmyuwpwlvqwr",
"last_name": "sitcpscqldzsnrwtujwvlxjklqp",
"referred_by": "architecto",
"tags": [
"ngzmiyvdljnikhwa"
],
"referrer": {
"first_name": "architecto",
"last_name": "architecto",
"email": "zbailey@example.net",
"phone": "architecto",
"location": "architecto"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/prospects';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'email' => 'gbailey@example.net',
'first_name' => 'miyvdljnikhwaykcmyuwpwlvqwr',
'last_name' => 'sitcpscqldzsnrwtujwvlxjklqp',
'referred_by' => 'architecto',
'tags' => ['ngzmiyvdljnikhwa'],
'referrer' => ['first_name' => 'architecto', 'last_name' => 'architecto', 'email' => 'zbailey@example.net', 'phone' => 'architecto', 'location' => 'architecto'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "lead-info@loopspark.com",
"email": "info@loopspark.com",
"first_name": "Luca",
"last_name": "Critelli",
"mobile_phone": null,
"creation_date": "2020-01-29T11:55:09+00:00",
"tags": [
{
"name": "signed-up",
"assigned_at": "2020-01-29T11:55:09+00:00"
},
{
"name": "awesome-client",
"assigned_at": "2020-01-29T11:55:09+00:00"
}
]
}
}
Example response (400):
{
"error": true,
"message": "The Prospect already exists"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a prospect
requires authentication
Returns the information about a specific prospect
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "lead-mhamill@marvin.net",
"profile_url": "https://loopspark.com/go/client/8863/lead-mhamill@marvin.net",
"email": "mhamill@marvin.net",
"first_name": "Rhett",
"last_name": "Kuhn",
"mobile_phone": "13573900260",
"creation_date": "1996-08-19T01:34:47+00:00",
"referred_by": null,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a Prospect
requires authentication
Updates the information of a Prospect
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/21224/prospects/jack@bauer.com" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"subscribed_email\": false,
\"subscribed_sms\": false,
\"period_count\": 27,
\"club_count\": 5,
\"referrer\": {
\"first_name\": \"John\",
\"last_name\": \"Doe\",
\"email\": \"john.doe@example.com\",
\"phone\": \"555-123-4567\",
\"location\": \"New York\"
}
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21224/prospects/jack@bauer.com"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"subscribed_email": false,
"subscribed_sms": false,
"period_count": 27,
"club_count": 5,
"referrer": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "555-123-4567",
"location": "New York"
}
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21224/prospects/jack@bauer.com';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'subscribed_email' => false,
'subscribed_sms' => false,
'period_count' => 27,
'club_count' => 5.0,
'referrer' => ['first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john.doe@example.com', 'phone' => '555-123-4567', 'location' => 'New York'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "lead-rosario67@hotmail.com",
"profile_url": "https://loopspark.com/go/client/117/lead-rosario67@hotmail.com",
"email": "rosario67@hotmail.com",
"first_name": "Kayden",
"last_name": "Robel",
"mobile_phone": "13572103900",
"creation_date": "2021-02-03T11:32:02+00:00",
"referred_by": null,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true
}
}
Example response (422, Trying to SMS opt in a prospect that manually opted out):
{
"error": true,
"message": "The prospect cannot be opted-in to SMS because they sent the opt-out text"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tags
Add a Tag
requires authentication
Assigns a Tag to a specific Prospect
Remove a Tag
requires authentication
Removes a Tag from a specific Prospect.
If the Prospect does not have the Tag assigned, it will return an error 400
Custom Fields
Get Prospect Custom Fields
requires authentication
Retrieve all custom field values for a specific prospect. Returns field definition along with current value.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "EMERGENCY_CONTACT",
"value": "Jane Doe - 555-1234"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Prospect Custom Fields
requires authentication
Batch update custom field values for a specific prospect. Can update multiple fields in a single request.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"fields\": [
{
\"id\": \"EMERGENCY_CONTACT\",
\"value\": \"John Doe - 555-1234\"
},
{
\"id\": \"THREE_CS\",
\"value\": \"challenge\"
}
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"fields": [
{
"id": "EMERGENCY_CONTACT",
"value": "John Doe - 555-1234"
},
{
"id": "THREE_CS",
"value": "challenge"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/prospects/john.doe@example.com/custom-fields';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'fields' => [
['id' => 'EMERGENCY_CONTACT', 'value' => 'John Doe - 555-1234'],
['id' => 'THREE_CS', 'value' => 'challenge'],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "EMERGENCY_CONTACT",
"value": "Jane Doe - 555-5678"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Notes
Create a Note
requires authentication
Adds a note to a Prospect, found by email (not case-sensitive), dated now. The note is plain text: HTML is shown as text, and line breaks are kept.
To categorize it, send the id of one of the studio's active note categories (see "List Note Categories").
Each call creates a new note, so do not retry a request that already succeeded.
The note has no staff author; LoopSpark shows its author as "Integration".
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/notes" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"note\": \"Called about the 6-week challenge.\",
\"note_category_id\": \"9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/notes"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"note": "Called about the 6-week challenge.",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/notes';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'note' => 'Called about the 6-week challenge.',
'note_category_id' => '9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"note": "<p>Called about the 6-week challenge.</p>",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-09-28T14:05:00+00:00"
}
}
Example response (422):
{
"message": "The note category is not available for this studio.",
"errors": {
"note_category_id": [
"The note category is not available for this studio."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Messages
Send a Text Message
requires authentication
Sends one SMS to a Prospect, found by email (not case-sensitive), from the studio's texting number, in the same conversation thread staff use in LoopSpark.
Placeholders are filled in as in LoopSpark: write the name in double curly braces, for example FIRST_NAME. The message can be at most 3 SMS segments long, counted before placeholders are filled in.
Opt-outs (STOP) and the usual opt-out notice work exactly as for a staff send, and the text counts against the studio's SMS bundle.
A 201 means the text was queued; delivery happens asynchronously. Each call sends a new text, so do not retry a request that already succeeded.
The text has no staff sender; the LoopSpark web app shows its sender as "Integration".
Returns 422 when the person is muted, has opted out of texts, or has no valid mobile number, or when the text is empty once placeholders are filled in.
Returns 403 when the studio is suspended.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/messages" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"message\": \"Hi there, see you at the 6pm class!\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/messages"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"message": "Hi there, see you at the 6pm class!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/prospects/jack@bauer.com/messages';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'message' => 'Hi there, see you at the 6pm class!',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"discussion_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-10-02T14:05:00+00:00"
}
}
Example response (422):
{
"message": "This client cannot be contacted by text message",
"errors": {
"message": [
"This client cannot be contacted by text message"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Unsubscribe Rules
All the Endpoints regarding the unsubscribe rules of a specific studio.
This feature allows you to tell LoopSpark that when we see a client or prospect with the specified UniqueID,
LoopSpark should consider them (un)subscribed on SMS and/or Email. Prospects' UniqueID can be retrieved from the "Prospect list"
endpoint, using the returned id of the prospect
Add (un)subscribe rule
requires authentication
Add one or multiple (un)subscribe rules for a specific Studio. If an Unsubscribe Rule is already present for the specified client ID, it will be updated.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"clients\": [
{
\"id\": \"1000017\",
\"unsubscribe\": true,
\"type\": \"email\"
}
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"clients": [
{
"id": "1000017",
"unsubscribe": true,
"type": "email"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'clients' => [
['id' => '1000017', 'unsubscribe' => true, 'type' => 'email'],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"client_id": "681",
"unsubscribe": true,
"type": "sms",
"valid_since": "2026-10-03T08:00:36+00:00"
},
{
"client_id": "369",
"unsubscribe": true,
"type": "sms",
"valid_since": "2026-10-03T08:00:36+00:00"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Remove unsubscribe rule
requires authentication
Removes any unsubscribe rule for a specific Client
Example request:
curl --request DELETE \
"https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients/10000012" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"type\": \"email\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients/10000012"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"type": "email"
};
fetch(url, {
method: "DELETE",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/unsubscribe_clients/10000012';
$response = $client->delete(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'type' => 'email',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"error": false,
"deleted": true,
"success": true
}
Example response (400):
{
"error": true,
"message": "No rule was found with that unique id and type combination"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Groups
All the Endpoints regarding the groups and the Staff assigned to them.
List Groups of a Studio
requires authentication
List the Groups of a Studio and the respective Staff members
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/groups" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/groups"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/groups';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": 1,
"name": "foobar",
"staff": [
{
"name": "Dr. Zane Roberts Sr.",
"email": "foo@current.com",
"phone": null
},
{
"name": "Kendrick O'Kon",
"email": "stehr.ciara@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "frontdesk",
"staff": [
{
"name": "Miss Megane Feeney",
"email": "myriam99@example.net",
"phone": null
},
{
"name": "Benedict Medhurst",
"email": "cassin.adrianna@example.net",
"phone": null
},
{
"name": "Roderick Lebsack",
"email": "gabriella.bogisich@example.net",
"phone": null
},
{
"name": "Okey Grady",
"email": "tpagac@example.org",
"phone": null
}
]
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a Group
requires authentication
Use this endpoint to create a Group. If the Group is already present, the passed Staff will be added to the Group.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/groups" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"group_name\": \"frontdesk\",
\"staff\": [
\"gbailey@example.net\"
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/groups"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"group_name": "frontdesk",
"staff": [
"gbailey@example.net"
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/groups';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'group_name' => 'frontdesk',
'staff' => ['gbailey@example.net'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201, Successfully created):
{
"data": {
"id": 14,
"name": "frontdesk",
"staff": [
{
"name": "Jane Doe",
"email": "frontdesk@studio.com",
"phone": null
},
{
"name": "Jack Bauer",
"email": "jack@bauer.com",
"phone": null
}
]
}
}
Example response (422, Staff is not part of the Studio):
{
"message": "The given data was invalid.",
"errors": {
"staff.1": [
"Staff member does not exist"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Group Members
requires authentication
Use this endpoint to update the Staff members assigned to a specific Group. This will detach any other Staff from the Group, and assign only the ones specified in the staff array
Example request:
curl --request PATCH \
"https://loopspark.com/api/franchise/v1/21225/groups" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"group_name\": \"frontdesk\",
\"staff\": [
\"gbailey@example.net\"
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/groups"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"group_name": "frontdesk",
"staff": [
"gbailey@example.net"
]
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/groups';
$response = $client->patch(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'group_name' => 'frontdesk',
'staff' => ['gbailey@example.net'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201, Successfully updated):
{
"data": {
"id": 14,
"name": "frontdesk",
"staff": [
{
"name": "Jane Doe",
"email": "frontdesk@studio.com",
"phone": null
},
{
"name": "Jack Bauer",
"email": "jack@bauer.com",
"phone": null
}
]
}
}
Example response (422, Staff is not part of the Studio):
{
"message": "The given data was invalid.",
"errors": {
"staff.1": [
"Staff member does not exist"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Studio
All the Endpoints regarding the studios belonging to your franchise.
Studio list
requires authentication
Retrieve the list of all the studios available. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/studios?per_page=10&sort_by=id&sort_direction=asc" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/studios"
);
const params = {
"per_page": "10",
"sort_by": "id",
"sort_direction": "asc",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/studios';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'sort_by' => 'id',
'sort_direction' => 'asc',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "739",
"name": "Dr. Courtney D'Amore II",
"api_set": true,
"phone_number": null,
"timezone": "UTC",
"website": "http://www.mosciski.com/dolorem-expedita-vero-natus-laudantium-delectus-sunt-neque",
"creation_date": "2026-10-03T08:00:35+00:00",
"start_date": null,
"first_import_done": true,
"is_template": false,
"outgoing_settings": {
"email": {
"signature": "alias",
"sender": "xokuneva@eichmann.com",
"sender_name": "Aracely Yost"
},
"phone": {
"sender_name": "10912332112",
"phone_forward": null
},
"address": "78972 Floyd Row\nSouth Fayetown, OR 63567-4075"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=739&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=739&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=739&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"period_count": {
"type": "ALL_TIME",
"day": 0
},
"manual_club_count": false
},
{
"id": "5718",
"name": "Violet Kub",
"api_set": true,
"phone_number": null,
"timezone": "UTC",
"website": "http://kozey.com/",
"creation_date": "2026-10-03T08:00:35+00:00",
"start_date": null,
"first_import_done": true,
"is_template": false,
"outgoing_settings": {
"email": {
"signature": "rerum",
"sender": "emarvin@yahoo.com",
"sender_name": "Gaetano Altenwerth"
},
"phone": {
"sender_name": "13506254432",
"phone_forward": null
},
"address": "37248 Wuckert View\nWest Francisco, NC 09772-5660"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=5718&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=5718&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=5718&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"period_count": {
"type": "ALL_TIME",
"day": 0
},
"manual_club_count": false
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a Studio
requires authentication
Shows the details of a specific studio. It shows also a list of the staff members assigned to this Studio and their respective role
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/studios/21225" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/studios/21225"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/studios/21225';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "foobar",
"name": "My Yoga Studio",
"phone_number": "12225551234",
"api_set": true,
"website": "https://mystudioisgreat.com/landing",
"timezone": "UTC",
"start_date": "2020-09-30",
"creation_date": "2018-02-22T13:00:00+00:00",
"first_import_done": true,
"outgoing_settings": {
"email": {
"signature": "et",
"sender": "umonahan@hotmail.com",
"sender_name": "Prof. Nels Quitzon"
},
"phone": {
"sender_name": "16273854741",
"phone_forward": "12125558888"
},
"address": "4800 Oak Grove Dr, Pasadena, CA 91109"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"price": "$10",
"name": "Great Package",
"discount_code": "CouponName",
"discount_price": "20 dollars"
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"staff": [
{
"name": "Krystal Harvey",
"email": "alexandrea87@example.com",
"role": "owner"
}
],
"groups": [
{
"id": 1,
"name": "front-desk",
"staff": [
{
"name": "Katarina Funk",
"email": "john42@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "teachers",
"staff": []
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a Studio
requires authentication
Update a specific studio information
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/studios/21225" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"name\": \"bngzmiyvdljnikhw\",
\"timezone\": \"America\\/Indiana\\/Tell_City\",
\"start_date\": \"2020-09-30\",
\"email_signature\": \"architecto\",
\"email_sender\": \"zbailey@example.net\",
\"email_sender_name\": \"iyvdljnikhwaykcm\",
\"sms_sender\": \"yuwpwlvqwrsitcps\",
\"phone_forward\": \"15552221234\",
\"facebook_url\": \"http:\\/\\/mclaughlin.info\\/dolores-enim-non-facere-tempora\",
\"instagram_handle\": \"@loopspark\",
\"twitter_handle\": \"@loopspark\",
\"list_packages_url\": \"http:\\/\\/www.leffler.info\\/quis-adipisci-molestias-fugit-deleniti-distinctio-eum\",
\"list_contracts_url\": \"https:\\/\\/nitzsche.net\\/aliquam-veniam-corporis-dolorem-mollitia-deleniti-nemo.html\",
\"book_class_url\": \"https:\\/\\/www.balistreri.org\\/dignissimos-neque-blanditiis-odio\",
\"can_create_automations\": true,
\"manual_club_count\": true,
\"website\": \"https:\\/\\/bradtke.org\\/fugit-qui-repudiandae-laboriosam-est-alias.html\",
\"period_count_type\": \"API\",
\"period_count_day\": 1,
\"package_1_url\": \"http:\\/\\/howe.com\\/accusamus-ut-et-recusandae.html\",
\"package_1_price\": \"architecto\",
\"package_1_name\": \"architecto\",
\"package_1_discount_code\": \"architecto\",
\"package_1_discount_price\": \"architecto\",
\"package_2_url\": \"http:\\/\\/bailey.com\\/\",
\"package_2_price\": \"architecto\",
\"package_2_name\": \"architecto\",
\"package_2_discount_code\": \"architecto\",
\"package_2_discount_price\": \"architecto\",
\"package_3_url\": \"http:\\/\\/bailey.com\\/\",
\"package_3_price\": \"architecto\",
\"package_3_name\": \"architecto\",
\"package_3_discount_code\": \"architecto\",
\"package_3_discount_price\": \"architecto\",
\"package_4_url\": \"http:\\/\\/bailey.com\\/\",
\"package_4_price\": \"architecto\",
\"package_4_name\": \"architecto\",
\"package_4_discount_code\": \"architecto\",
\"package_4_discount_price\": \"architecto\",
\"package_5_url\": \"http:\\/\\/bailey.com\\/\",
\"package_5_price\": \"architecto\",
\"package_5_name\": \"architecto\",
\"package_5_discount_code\": \"architecto\",
\"package_5_discount_price\": \"architecto\",
\"contract_1_url\": \"http:\\/\\/bailey.com\\/\",
\"contract_1_price\": \"architecto\",
\"contract_1_name\": \"architecto\",
\"contract_1_discount_code\": \"architecto\",
\"contract_1_discount_price\": \"architecto\",
\"contract_2_url\": \"http:\\/\\/bailey.com\\/\",
\"contract_2_price\": \"architecto\",
\"contract_2_name\": \"architecto\",
\"contract_2_discount_code\": \"architecto\",
\"contract_2_discount_price\": \"architecto\",
\"contract_3_url\": \"http:\\/\\/bailey.com\\/\",
\"contract_3_price\": \"architecto\",
\"contract_3_name\": \"architecto\",
\"contract_3_discount_code\": \"architecto\",
\"contract_3_discount_price\": \"architecto\",
\"contract_4_url\": \"http:\\/\\/bailey.com\\/\",
\"contract_4_price\": \"architecto\",
\"contract_4_name\": \"architecto\",
\"contract_4_discount_code\": \"architecto\",
\"contract_4_discount_price\": \"architecto\",
\"contract_5_url\": \"http:\\/\\/bailey.com\\/\",
\"contract_5_price\": \"architecto\",
\"contract_5_name\": \"architecto\",
\"contract_5_discount_code\": \"architecto\",
\"contract_5_discount_price\": \"architecto\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/studios/21225"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "bngzmiyvdljnikhw",
"timezone": "America\/Indiana\/Tell_City",
"start_date": "2020-09-30",
"email_signature": "architecto",
"email_sender": "zbailey@example.net",
"email_sender_name": "iyvdljnikhwaykcm",
"sms_sender": "yuwpwlvqwrsitcps",
"phone_forward": "15552221234",
"facebook_url": "http:\/\/mclaughlin.info\/dolores-enim-non-facere-tempora",
"instagram_handle": "@loopspark",
"twitter_handle": "@loopspark",
"list_packages_url": "http:\/\/www.leffler.info\/quis-adipisci-molestias-fugit-deleniti-distinctio-eum",
"list_contracts_url": "https:\/\/nitzsche.net\/aliquam-veniam-corporis-dolorem-mollitia-deleniti-nemo.html",
"book_class_url": "https:\/\/www.balistreri.org\/dignissimos-neque-blanditiis-odio",
"can_create_automations": true,
"manual_club_count": true,
"website": "https:\/\/bradtke.org\/fugit-qui-repudiandae-laboriosam-est-alias.html",
"period_count_type": "API",
"period_count_day": 1,
"package_1_url": "http:\/\/howe.com\/accusamus-ut-et-recusandae.html",
"package_1_price": "architecto",
"package_1_name": "architecto",
"package_1_discount_code": "architecto",
"package_1_discount_price": "architecto",
"package_2_url": "http:\/\/bailey.com\/",
"package_2_price": "architecto",
"package_2_name": "architecto",
"package_2_discount_code": "architecto",
"package_2_discount_price": "architecto",
"package_3_url": "http:\/\/bailey.com\/",
"package_3_price": "architecto",
"package_3_name": "architecto",
"package_3_discount_code": "architecto",
"package_3_discount_price": "architecto",
"package_4_url": "http:\/\/bailey.com\/",
"package_4_price": "architecto",
"package_4_name": "architecto",
"package_4_discount_code": "architecto",
"package_4_discount_price": "architecto",
"package_5_url": "http:\/\/bailey.com\/",
"package_5_price": "architecto",
"package_5_name": "architecto",
"package_5_discount_code": "architecto",
"package_5_discount_price": "architecto",
"contract_1_url": "http:\/\/bailey.com\/",
"contract_1_price": "architecto",
"contract_1_name": "architecto",
"contract_1_discount_code": "architecto",
"contract_1_discount_price": "architecto",
"contract_2_url": "http:\/\/bailey.com\/",
"contract_2_price": "architecto",
"contract_2_name": "architecto",
"contract_2_discount_code": "architecto",
"contract_2_discount_price": "architecto",
"contract_3_url": "http:\/\/bailey.com\/",
"contract_3_price": "architecto",
"contract_3_name": "architecto",
"contract_3_discount_code": "architecto",
"contract_3_discount_price": "architecto",
"contract_4_url": "http:\/\/bailey.com\/",
"contract_4_price": "architecto",
"contract_4_name": "architecto",
"contract_4_discount_code": "architecto",
"contract_4_discount_price": "architecto",
"contract_5_url": "http:\/\/bailey.com\/",
"contract_5_price": "architecto",
"contract_5_name": "architecto",
"contract_5_discount_code": "architecto",
"contract_5_discount_price": "architecto"
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/studios/21225';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'name' => 'bngzmiyvdljnikhw',
'timezone' => 'America/Indiana/Tell_City',
'start_date' => '2020-09-30',
'email_signature' => 'architecto',
'email_sender' => 'zbailey@example.net',
'email_sender_name' => 'iyvdljnikhwaykcm',
'sms_sender' => 'yuwpwlvqwrsitcps',
'phone_forward' => '15552221234',
'facebook_url' => 'http://mclaughlin.info/dolores-enim-non-facere-tempora',
'instagram_handle' => '@loopspark',
'twitter_handle' => '@loopspark',
'list_packages_url' => 'http://www.leffler.info/quis-adipisci-molestias-fugit-deleniti-distinctio-eum',
'list_contracts_url' => 'https://nitzsche.net/aliquam-veniam-corporis-dolorem-mollitia-deleniti-nemo.html',
'book_class_url' => 'https://www.balistreri.org/dignissimos-neque-blanditiis-odio',
'can_create_automations' => true,
'manual_club_count' => true,
'website' => 'https://bradtke.org/fugit-qui-repudiandae-laboriosam-est-alias.html',
'period_count_type' => 'API',
'period_count_day' => 1.0,
'package_1_url' => 'http://howe.com/accusamus-ut-et-recusandae.html',
'package_1_price' => 'architecto',
'package_1_name' => 'architecto',
'package_1_discount_code' => 'architecto',
'package_1_discount_price' => 'architecto',
'package_2_url' => 'http://bailey.com/',
'package_2_price' => 'architecto',
'package_2_name' => 'architecto',
'package_2_discount_code' => 'architecto',
'package_2_discount_price' => 'architecto',
'package_3_url' => 'http://bailey.com/',
'package_3_price' => 'architecto',
'package_3_name' => 'architecto',
'package_3_discount_code' => 'architecto',
'package_3_discount_price' => 'architecto',
'package_4_url' => 'http://bailey.com/',
'package_4_price' => 'architecto',
'package_4_name' => 'architecto',
'package_4_discount_code' => 'architecto',
'package_4_discount_price' => 'architecto',
'package_5_url' => 'http://bailey.com/',
'package_5_price' => 'architecto',
'package_5_name' => 'architecto',
'package_5_discount_code' => 'architecto',
'package_5_discount_price' => 'architecto',
'contract_1_url' => 'http://bailey.com/',
'contract_1_price' => 'architecto',
'contract_1_name' => 'architecto',
'contract_1_discount_code' => 'architecto',
'contract_1_discount_price' => 'architecto',
'contract_2_url' => 'http://bailey.com/',
'contract_2_price' => 'architecto',
'contract_2_name' => 'architecto',
'contract_2_discount_code' => 'architecto',
'contract_2_discount_price' => 'architecto',
'contract_3_url' => 'http://bailey.com/',
'contract_3_price' => 'architecto',
'contract_3_name' => 'architecto',
'contract_3_discount_code' => 'architecto',
'contract_3_discount_price' => 'architecto',
'contract_4_url' => 'http://bailey.com/',
'contract_4_price' => 'architecto',
'contract_4_name' => 'architecto',
'contract_4_discount_code' => 'architecto',
'contract_4_discount_price' => 'architecto',
'contract_5_url' => 'http://bailey.com/',
'contract_5_price' => 'architecto',
'contract_5_name' => 'architecto',
'contract_5_discount_code' => 'architecto',
'contract_5_discount_price' => 'architecto',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "foobar",
"name": "My Yoga Studio",
"phone_number": "12225551234",
"api_set": true,
"website": "https://mystudioisgreat.com/landing",
"timezone": "UTC",
"start_date": "2020-09-30",
"creation_date": "2018-02-22T13:00:00+00:00",
"first_import_done": true,
"outgoing_settings": {
"email": {
"signature": "et",
"sender": "umonahan@hotmail.com",
"sender_name": "Prof. Nels Quitzon"
},
"phone": {
"sender_name": "16273854741",
"phone_forward": "12125558888"
},
"address": "4800 Oak Grove Dr, Pasadena, CA 91109"
},
"social": {
"facebook_url": null,
"instagram_handle": null,
"twitter_handle": null
},
"urls": {
"list_packages_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"list_contracts_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=40",
"book_class_url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=-7"
},
"variables": {
"packages": [
{
"package_1": {
"url": "https://clients.mindbodyonline.com/classic/ws?studioid=foobar&stype=41",
"price": "$10",
"name": "Great Package",
"discount_code": "CouponName",
"discount_price": "20 dollars"
}
},
{
"package_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"package_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
],
"contracts": [
{
"contract_1": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_2": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_3": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_4": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
},
{
"contract_5": {
"url": null,
"price": null,
"name": null,
"discount_code": null,
"discount_price": null
}
}
]
},
"staff": [
{
"name": "Krystal Harvey",
"email": "alexandrea87@example.com",
"role": "owner"
}
],
"groups": [
{
"id": 1,
"name": "front-desk",
"staff": [
{
"name": "Katarina Funk",
"email": "john42@example.com",
"phone": null
}
]
},
{
"id": 2,
"name": "teachers",
"staff": []
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Forms
All the Endpoints regarding the groups and the Staff assigned to them.
Forms list
requires authentication
Retrieve a list of the forms for a specific Studio. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/forms?per_page=10&sort_by=id&sort_direction=asc&category=&include_archived=" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/forms"
);
const params = {
"per_page": "10",
"sort_by": "id",
"sort_direction": "asc",
"category": "",
"include_archived": "0",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/forms';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'sort_by' => 'id',
'sort_direction' => 'asc',
'category' => '',
'include_archived' => '0',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "8818a7b7-d58c-4d9a-806d-bd382bb7dd52",
"name": "explicabo",
"category": "non",
"archived": false,
"creates_pdf": false,
"form_url": "https://lo.ls/IdC1wXm",
"send_email_copy_to": null,
"allows_new_clients": false,
"thank_you_url": null
},
{
"id": "e4bf9b25-8d13-46b2-a66f-aa26aeaf6fae",
"name": "et",
"category": "assumenda",
"archived": false,
"creates_pdf": false,
"form_url": "https://lo.ls/MQa5UUA",
"send_email_copy_to": null,
"allows_new_clients": false,
"thank_you_url": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a single form
requires authentication
Returns the information of a specific form
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/forms/61f48cb0-9b59-4ac5-8a46-c4192a5e750b" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/forms/61f48cb0-9b59-4ac5-8a46-c4192a5e750b"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/forms/61f48cb0-9b59-4ac5-8a46-c4192a5e750b';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "c4dece0b-6ac9-4961-9dc2-51fa495788b6",
"name": "aut",
"category": "adipisci",
"archived": false,
"creates_pdf": false,
"form_url": "https://lo.ls/paorvUv",
"send_email_copy_to": null,
"allows_new_clients": false,
"thank_you_url": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Client
All the Endpoints regarding the clients of a specific studio.
Client list
requires authentication
Retrieve a list of clients for a specific Studio. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/123/clients?per_page=10&page=1&sort_by=id&sort_direction=asc&email=&mobile_phone=&opted_out_emails=1&opted_out_sms=1&tags[]=signed-up" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/123/clients"
);
const params = {
"per_page": "10",
"page": "1",
"sort_by": "id",
"sort_direction": "asc",
"email": "",
"mobile_phone": "",
"opted_out_emails": "1",
"opted_out_sms": "1",
"tags[0]": "signed-up",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/123/clients';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'page' => '1',
'sort_by' => 'id',
'sort_direction' => 'asc',
'email' => '',
'mobile_phone' => '',
'opted_out_emails' => '1',
'opted_out_sms' => '1',
'tags[0]' => 'signed-up',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "2137449171",
"team_id": "6789",
"profile_url": "https://loopspark.com/go/client/6789/2137449171",
"email": "kconsidine@hotmail.com",
"active": true,
"first_name": "Pauline",
"last_name": "Schultz",
"mobile_phone": "13577773167",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "North Eileen",
"address": "4706 Madisen Avenue Suite 636\nNew Josianetown, WV 49408-0159",
"postal_code": "79150-6985",
"state": "CA"
},
"emergency_contact": {
"email": "mitchell.matilda@yahoo.com",
"name": "Mina Bauch",
"phone": "13573073143",
"relationship": "wife"
},
"birthday": "1972-08-25T23:37:54+00:00",
"creation_date": "2023-10-10T09:48:20+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
},
{
"id": "1796147067",
"team_id": "5854",
"profile_url": "https://loopspark.com/go/client/5854/1796147067",
"email": "tristin.mohr@leffler.com",
"active": true,
"first_name": "Vella",
"last_name": "Ziemann",
"mobile_phone": "13577240472",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "Maraborough",
"address": "753 Jakubowski Track\nLake Daronside, MT 65023-7717",
"postal_code": "31500-3812",
"state": "CA"
},
"emergency_contact": {
"email": "noe.ankunding@hotmail.com",
"name": "Prof. Kenna Ortiz",
"phone": "13571889386",
"relationship": "wife"
},
"birthday": "2005-10-28T06:49:30+00:00",
"creation_date": "2024-05-25T01:27:05+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a client
requires authentication
Returns the information about a specific client
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/clients/10000023" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/clients/10000023"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/clients/10000023';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "4274032",
"team_id": "2603",
"profile_url": "https://loopspark.com/go/client/2603/4274032",
"email": "green.doris@gmail.com",
"active": true,
"first_name": "Emanuel",
"last_name": "Emmerich",
"mobile_phone": "13573996721",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "New Monaland",
"address": "422 Moen Corners\nSouth Moises, NH 64519",
"postal_code": "91956",
"state": "CA"
},
"emergency_contact": {
"email": "kirsten.ward@yahoo.com",
"name": "Hank Batz",
"phone": "13570216494",
"relationship": "wife"
},
"birthday": "1990-07-16T02:01:30+00:00",
"creation_date": "2024-11-19T16:36:21+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a Client
requires authentication
Updates the information of a Client
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/21224/clients/10000024" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"subscribed_email\": false,
\"subscribed_sms\": false,
\"period_count\": 27,
\"club_count\": 5,
\"referrer\": {
\"first_name\": \"John\",
\"last_name\": \"Doe\",
\"email\": \"john.doe@example.com\",
\"phone\": \"555-123-4567\",
\"location\": \"New York\"
}
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21224/clients/10000024"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"subscribed_email": false,
"subscribed_sms": false,
"period_count": 27,
"club_count": 5,
"referrer": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "555-123-4567",
"location": "New York"
}
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21224/clients/10000024';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'subscribed_email' => false,
'subscribed_sms' => false,
'period_count' => 27,
'club_count' => 5.0,
'referrer' => ['first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john.doe@example.com', 'phone' => '555-123-4567', 'location' => 'New York'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "524453158",
"team_id": "9290",
"profile_url": "https://loopspark.com/go/client/9290/524453158",
"email": "qankunding@gmail.com",
"active": true,
"first_name": "Elton",
"last_name": "Gaylord",
"mobile_phone": "13576738443",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "South Dorisfurt",
"address": "5805 Noah Via Suite 679\nSouth Katlyn, VA 78637",
"postal_code": "80568",
"state": "CA"
},
"emergency_contact": {
"email": "vraynor@labadie.com",
"name": "Mr. Carey Smitham",
"phone": "13573341494",
"relationship": "wife"
},
"birthday": "1986-10-21T08:44:51+00:00",
"creation_date": "2026-08-12T05:46:43+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": false,
"can_contact_by_sms": false,
"unsubscribed_email_at": "2026-10-03T08:00:35+00:00",
"unsubscribed_sms_at": "2026-10-03T08:00:35+00:00",
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
}
}
Example response (403, Trying to update club_count when manual_club_count is disabled):
{
"error": true,
"message": "Cannot update club_count when manual_club_count is disabled for the studio"
}
Example response (422, Trying to SMS opt in a client that manually opted out):
{
"error": true,
"message": "The client cannot be opted-in to SMS because they sent the opt-out text"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tags
Add a Tag
requires authentication
Assigns a Tag to a specific Client
Remove a Tag
requires authentication
Removes a Tag from a specific Client.
If the Client does not have the Tag assigned, it will return an error 400
Forms
Lists the form submissions
requires authentication
Lists the form submissions of a specific client
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/clients/10000023/form_submissions?page=1&per_page=10&sort_by=creation_date&sort_direction=asc&form_id=&category=" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/clients/10000023/form_submissions"
);
const params = {
"page": "1",
"per_page": "10",
"sort_by": "creation_date",
"sort_direction": "asc",
"form_id": "",
"category": "",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/clients/10000023/form_submissions';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'page' => '1',
'per_page' => '10',
'sort_by' => 'creation_date',
'sort_direction' => 'asc',
'form_id' => '',
'category' => '',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "a621f6a5-219b-4a92-bc68-04a8b423d629",
"submission_date": "2026-10-03T08:00:35+00:00",
"pdf_url": "http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
"form": {
"id": "d0ae0146-ed1a-47b8-9d38-d39cf6e0b512",
"name": "rem",
"category": "ea",
"archived": false,
"creates_pdf": false,
"form_url": "https://lo.ls/C99cQnt",
"send_email_copy_to": null,
"allows_new_clients": false,
"thank_you_url": null
}
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Custom Fields
Get Client Custom Fields
requires authentication
Retrieve all custom field values for a specific client. Returns field definition along with current value.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"custom_field": {
"id": "EMERGENCY_CONTACT",
"name": "Emergency Contact",
"type": "string",
"field_options": null,
"description": "Person to contact in case of emergency"
},
"value": "John Doe - 555-1234"
},
{
"custom_field": {
"id": "THREE_CS",
"name": "Three Cs",
"type": "single_select",
"field_options": [
{
"value": "challenge",
"label": "Challenge",
"is_active": true,
"sort_order": 1
},
{
"value": "commitment",
"label": "Commitment",
"is_active": true,
"sort_order": 2
},
{
"value": "community",
"label": "Community",
"is_active": true,
"sort_order": 3
}
],
"description": "Select primary motivation"
},
"value": "challenge"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Client Custom Fields
requires authentication
Batch update custom field values for a specific client. Can update multiple fields in a single request.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"fields\": [
{
\"id\": \"EMERGENCY_CONTACT\",
\"value\": \"John Doe - 555-1234\"
},
{
\"id\": \"THREE_CS\",
\"value\": \"challenge\"
}
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"fields": [
{
"id": "EMERGENCY_CONTACT",
"value": "John Doe - 555-1234"
},
{
"id": "THREE_CS",
"value": "challenge"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/clients/100000001/custom-fields';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'fields' => [
['id' => 'EMERGENCY_CONTACT', 'value' => 'John Doe - 555-1234'],
['id' => 'THREE_CS', 'value' => 'challenge'],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"custom_field": {
"id": "EMERGENCY_CONTACT",
"name": "Emergency Contact",
"type": "string"
},
"value": "Jane Doe - 555-5678"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Notes
Create a Note
requires authentication
Adds a note to a Client, dated now. The note is plain text: HTML is shown as text, and line breaks are kept.
To categorize it, send the id of one of the studio's active note categories (see "List Note Categories").
Each call creates a new note, so do not retry a request that already succeeded.
The note has no staff author; LoopSpark shows its author as "Integration".
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/clients/10000023/notes" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"note\": \"Called about the 6-week challenge.\",
\"note_category_id\": \"9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/clients/10000023/notes"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"note": "Called about the 6-week challenge.",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/clients/10000023/notes';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'note' => 'Called about the 6-week challenge.',
'note_category_id' => '9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"note": "<p>Called about the 6-week challenge.</p>",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-09-28T14:05:00+00:00"
}
}
Example response (422):
{
"message": "The note category is not available for this studio.",
"errors": {
"note_category_id": [
"The note category is not available for this studio."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Messages
Send a Text Message
requires authentication
Sends one SMS to a Client from the studio's texting number, in the same conversation thread staff use in LoopSpark.
Placeholders are filled in as in LoopSpark: write the name in double curly braces, for example FIRST_NAME. The message can be at most 3 SMS segments long, counted before placeholders are filled in.
Opt-outs (STOP) and the usual opt-out notice work exactly as for a staff send, and the text counts against the studio's SMS bundle.
A 201 means the text was queued; delivery happens asynchronously. Each call sends a new text, so do not retry a request that already succeeded.
The text has no staff sender; the LoopSpark web app shows its sender as "Integration".
Returns 422 when the person is muted, has opted out of texts, or has no valid mobile number, or when the text is empty once placeholders are filled in.
Returns 403 when the studio is suspended.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/clients/10000023/messages" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"message\": \"Hi there, see you at the 6pm class!\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/clients/10000023/messages"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"message": "Hi there, see you at the 6pm class!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/clients/10000023/messages';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'message' => 'Hi there, see you at the 6pm class!',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"discussion_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-10-02T14:05:00+00:00"
}
}
Example response (422):
{
"message": "This client cannot be contacted by text message",
"errors": {
"message": [
"This client cannot be contacted by text message"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Contacts
Endpoints related to managing studio contacts, encompassing both clients and prospects. A contact is a unified entity designed to facilitate seamless viewing and management of both clients and prospects within a single list. This makes it easier to find clients or prospects in the system with just one call.
Contacts list
requires authentication
Retrieve a list of all the contacts for a specific Studio. The results are paginated
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/123/contacts?per_page=10&page=1&sort_by=id&sort_direction=asc&email=&mobile_phone=&opted_out_emails=1&opted_out_sms=1&tags[]=signed-up" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/123/contacts"
);
const params = {
"per_page": "10",
"page": "1",
"sort_by": "id",
"sort_direction": "asc",
"email": "",
"mobile_phone": "",
"opted_out_emails": "1",
"opted_out_sms": "1",
"tags[0]": "signed-up",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/123/contacts';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'page' => '1',
'sort_by' => 'id',
'sort_direction' => 'asc',
'email' => '',
'mobile_phone' => '',
'opted_out_emails' => '1',
'opted_out_sms' => '1',
'tags[0]' => 'signed-up',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "lead-mhamill@marvin.net",
"team_id": "8863",
"profile_url": "https://loopspark.com/go/client/8863/lead-mhamill@marvin.net",
"email": "mhamill@marvin.net",
"active": false,
"first_name": "Rhett",
"last_name": "Kuhn",
"mobile_phone": "13573900260",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": true,
"created_at": "2026-10-03T08:00:36+00:00",
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": null
},
{
"id": "lead-conn.brisa@yahoo.com",
"team_id": "751",
"profile_url": "https://loopspark.com/go/client/751/lead-conn.brisa@yahoo.com",
"email": "conn.brisa@yahoo.com",
"active": false,
"first_name": "Ethelyn",
"last_name": "Lesch",
"mobile_phone": "13570269273",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": true,
"created_at": "2026-10-03T08:00:36+00:00",
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": null
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a Contact
requires authentication
Returns the information about a specific Contact
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/contacts/10000023" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/contacts/10000023"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/contacts/10000023';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "lead-carter.raleigh@kreiger.info",
"team_id": "3089",
"profile_url": "https://loopspark.com/go/client/3089/lead-carter.raleigh@kreiger.info",
"email": "carter.raleigh@kreiger.info",
"active": false,
"first_name": "Callie",
"last_name": "Towne",
"mobile_phone": "13576039800",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": true,
"created_at": "2026-10-03T08:00:36+00:00",
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a Contact
requires authentication
Updates the information of a Contact
Example request:
curl --request PUT \
"https://loopspark.com/api/franchise/v1/21224/contacts/10000024" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"subscribed_email\": false,
\"subscribed_sms\": false,
\"period_count\": 27,
\"club_count\": 5,
\"referrer\": {
\"first_name\": \"John\",
\"last_name\": \"Doe\",
\"email\": \"john.doe@example.com\",
\"phone\": \"555-123-4567\",
\"location\": \"New York\"
}
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21224/contacts/10000024"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"subscribed_email": false,
"subscribed_sms": false,
"period_count": 27,
"club_count": 5,
"referrer": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "555-123-4567",
"location": "New York"
}
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21224/contacts/10000024';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'subscribed_email' => false,
'subscribed_sms' => false,
'period_count' => 27,
'club_count' => 5.0,
'referrer' => ['first_name' => 'John', 'last_name' => 'Doe', 'email' => 'john.doe@example.com', 'phone' => '555-123-4567', 'location' => 'New York'],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": {
"id": "lead-mhamill@marvin.net",
"team_id": "8863",
"profile_url": "https://loopspark.com/go/client/8863/lead-mhamill@marvin.net",
"email": "mhamill@marvin.net",
"active": false,
"first_name": "Rhett",
"last_name": "Kuhn",
"mobile_phone": "13573900260",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": true,
"created_at": "2026-10-03T08:00:36+00:00",
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": null
}
}
Example response (422, Trying to SMS opt in a contact that manually opted out):
{
"error": true,
"message": "The contact cannot be opted-in to SMS because they sent the opt-out text"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tags
Add a Tag
requires authentication
Assigns a Tag to a specific Contact
Remove a Tag
requires authentication
Removes a Tag from a specific Contact.
If the Contact does not have the Tag assigned, it will return an error 400
Custom Fields
Get Contact Custom Fields
requires authentication
Retrieve all custom field values for a specific contact. Returns field definition along with current value.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "EMERGENCY_CONTACT",
"value": "Jane Doe - 555-1234"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update Contact Custom Fields
requires authentication
Batch update custom field values for a specific contact. Can update multiple fields in a single request.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"fields\": [
{
\"id\": \"EMERGENCY_CONTACT\",
\"value\": \"John Doe - 555-1234\"
},
{
\"id\": \"THREE_CS\",
\"value\": \"challenge\"
}
]
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"fields": [
{
"id": "EMERGENCY_CONTACT",
"value": "John Doe - 555-1234"
},
{
"id": "THREE_CS",
"value": "challenge"
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/100001/contacts/100000001/custom-fields';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'fields' => [
['id' => 'EMERGENCY_CONTACT', 'value' => 'John Doe - 555-1234'],
['id' => 'THREE_CS', 'value' => 'challenge'],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "EMERGENCY_CONTACT",
"value": "Jane Doe - 555-5678"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Notes
Create a Note
requires authentication
Adds a note to a Contact (a Client or a Prospect), found by its contact id, dated now. The note is plain text: HTML is shown as text, and line breaks are kept.
To categorize it, send the id of one of the studio's active note categories (see "List Note Categories").
Each call creates a new note, so do not retry a request that already succeeded.
The note has no staff author; LoopSpark shows its author as "Integration".
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/contacts/10000023/notes" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"note\": \"Called about the 6-week challenge.\",
\"note_category_id\": \"9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/contacts/10000023/notes"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"note": "Called about the 6-week challenge.",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/contacts/10000023/notes';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'note' => 'Called about the 6-week challenge.',
'note_category_id' => '9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"note": "<p>Called about the 6-week challenge.</p>",
"note_category_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-09-28T14:05:00+00:00"
}
}
Example response (422):
{
"message": "The note category is not available for this studio.",
"errors": {
"note_category_id": [
"The note category is not available for this studio."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Messages
Send a Text Message
requires authentication
Sends one SMS to a Contact (a Client or a Prospect), found by its contact id, from the studio's texting number, in the same conversation thread staff use in LoopSpark.
Placeholders are filled in as in LoopSpark: write the name in double curly braces, for example FIRST_NAME. The message can be at most 3 SMS segments long, counted before placeholders are filled in.
Opt-outs (STOP) and the usual opt-out notice work exactly as for a staff send, and the text counts against the studio's SMS bundle.
A 201 means the text was queued; delivery happens asynchronously. Each call sends a new text, so do not retry a request that already succeeded.
The text has no staff sender; the LoopSpark web app shows its sender as "Integration".
Returns 422 when the person is muted, has opted out of texts, or has no valid mobile number, or when the text is empty once placeholders are filled in.
Returns 403 when the studio is suspended.
Example request:
curl --request POST \
"https://loopspark.com/api/franchise/v1/21225/contacts/10000023/messages" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}" \
--header "Content-Type: application/json" \
--data "{
\"message\": \"Hi there, see you at the 6pm class!\"
}"
const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/contacts/10000023/messages"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"message": "Hi there, see you at the 6pm class!"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/contacts/10000023/messages';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
'Content-Type' => 'application/json',
],
'json' => [
'message' => 'Hi there, see you at the 6pm class!',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (201):
{
"data": {
"id": "3f0d6a52-8a4e-4c43-9b1e-6f3c2a1d7e90",
"discussion_id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"created_at": "2026-10-02T14:05:00+00:00"
}
}
Example response (422):
{
"message": "This client cannot be contacted by text message",
"errors": {
"message": [
"This client cannot be contacted by text message"
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Custom Fields
Franchise API endpoints for managing custom field definitions and client values.
Get Custom Fields
requires authentication
Retrieve all custom field definitions for this franchise.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/custom-fields" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/custom-fields"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/custom-fields';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "EMERGENCY_CONTACT",
"name": "Emergency Contact",
"type": "string",
"field_options": null,
"description": "Person to contact in case of emergency",
"visible_in_ui": true,
"editable_by_staff": true,
"sort_order": 0
},
{
"id": "MEMBERSHIP_TYPE",
"name": "Membership Type",
"type": "single_select",
"field_options": [
{
"value": "basic",
"label": "Basic",
"is_active": true,
"sort_order": 1
},
{
"value": "premium",
"label": "Premium",
"is_active": true,
"sort_order": 2
}
],
"description": "Choose membership level",
"visible_in_ui": true,
"editable_by_staff": false,
"sort_order": 1
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Deferred Tags
All the Endpoints regarding the deferred tags of a specific studio.
This feature allows you to tell LoopSpark that when we see a client or prospect with the specified UniqueID,
LoopSpark should automatically add a Tag and attach it them. Prospects' UniqueID can be retrieved from the "Prospect list"
endpoint, using the returned id of the prospect
Add deferred tag
requires authentication
Add one or multiple deferred tag rules for a specific Studio. LoopSpark will automatically add the chosen tag when the rule is applied.
Remove Deferred Tag rule
requires authentication
Removes a defered tag rule for a specific Client
Note Categories
The studio's note categories, used to file a note created on a Client, Prospect or Contact.
List Note Categories
requires authentication
The studio's active note categories. Send a category's id as note_category_id when creating a note.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/21225/note-categories" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/21225/note-categories"
);
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/21225/note-categories';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "9b2f0c1e-4c1a-4a53-9a5e-2f7f3d1c8b10",
"name": "Objection Note"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Search
This endpoint allows franchise owners to search and list contacts (clients or prospects) across all studios within their franchise. It provides filtering by email, mobile phone, tags, and specific studio site IDs, as well as sorting and pagination capabilities. This is useful for managing contacts across multiple locations and finding duplicates or shared contacts between studios.
Search Contacts
requires authentication
Search for a Contact across the whole franchise.
Example request:
curl --request GET \
--get "https://loopspark.com/api/franchise/v1/search?per_page=10&page=1&sort_by=id&sort_direction=asc&email=&mobile_phone=&tags[]=signed-up&site_ids[]=" \
--header "Authorization: Bearer {YOUR_FRANCHISE_TOKEN}"const url = new URL(
"https://loopspark.com/api/franchise/v1/search"
);
const params = {
"per_page": "10",
"page": "1",
"sort_by": "id",
"sort_direction": "asc",
"email": "",
"mobile_phone": "",
"tags[0]": "signed-up",
"site_ids[0]": "",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_FRANCHISE_TOKEN}",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());$client = new \GuzzleHttp\Client();
$url = 'https://loopspark.com/api/franchise/v1/search';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_FRANCHISE_TOKEN}',
],
'query' => [
'per_page' => '10',
'page' => '1',
'sort_by' => 'id',
'sort_direction' => 'asc',
'email' => '',
'mobile_phone' => '',
'tags[0]' => 'signed-up',
'site_ids[0]' => '',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));Example response (200):
{
"data": [
{
"id": "822569775",
"team_id": "6789",
"profile_url": "https://loopspark.com/go/client/6789/822569775",
"email": "kconsidine@hotmail.com",
"active": true,
"first_name": "Pauline",
"last_name": "Schultz",
"mobile_phone": "13577773167",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": false,
"created_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": {
"id": "822569775",
"team_id": "6789",
"profile_url": "https://loopspark.com/go/client/6789/822569775",
"email": "kconsidine@hotmail.com",
"active": true,
"first_name": "Pauline",
"last_name": "Schultz",
"mobile_phone": "13577773167",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "North Eileen",
"address": "4706 Madisen Avenue Suite 636\nNew Josianetown, WV 49408-0159",
"postal_code": "79150-6985",
"state": "CA"
},
"emergency_contact": {
"email": "mitchell.matilda@yahoo.com",
"name": "Mina Bauch",
"phone": "13573073143",
"relationship": "wife"
},
"birthday": "1972-08-25T23:37:54+00:00",
"creation_date": "2023-10-10T09:48:20+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
}
},
{
"id": "26665356",
"team_id": "9537",
"profile_url": "https://loopspark.com/go/client/9537/26665356",
"email": "schmitt.eleanore@nienow.com",
"active": true,
"first_name": "Kenna",
"last_name": "Ortiz",
"mobile_phone": "13579799206",
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"is_prospect": false,
"created_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"client": {
"id": "26665356",
"team_id": "9537",
"profile_url": "https://loopspark.com/go/client/9537/26665356",
"email": "schmitt.eleanore@nienow.com",
"active": true,
"first_name": "Kenna",
"last_name": "Ortiz",
"mobile_phone": "13579799206",
"ltv": 0,
"phone_home": null,
"phone_work": null,
"referred_by": null,
"address": {
"city": "New Greyson",
"address": "6747 Wolf Light\nNew Brookeburgh, TX 37725",
"postal_code": "56023-5576",
"state": "CA"
},
"emergency_contact": {
"email": "ritchie.dorris@klocko.com",
"name": "Trinity Swaniawski",
"phone": "13572063041",
"relationship": "wife"
},
"birthday": "1987-05-12T05:35:52+00:00",
"creation_date": "2024-05-14T05:57:03+00:00",
"refreshed_at": null,
"tags": [
{
"name": "Member",
"assigned_at": "2025-01-15T09:30:00+00:00"
}
],
"custom_fields": [],
"can_contact_by_email": true,
"can_contact_by_sms": true,
"unsubscribed_email_at": null,
"unsubscribed_sms_at": null,
"unsubscribed_sms_text": false,
"referrer": {
"first_name": null,
"last_name": null,
"email": null,
"phone": null,
"location": null
},
"stats": {
"period_count": 12,
"club_count": 0,
"open_discussions": 0,
"open_todos": 0
},
"social": {
"twitter": [],
"linkedin": [],
"facebook": [],
"instagram": []
}
}
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.