Splits
Methods for splits: find the ones you need, create, start or stop, set your own offer links and pixels, clone and archive. For what a split is and how it divides traffic, see the "Splits and Flows" section. Which PWAs are in a split and with which weights is set by flows - see the API section "Flows".
| Method | Address | What it does |
|---|---|---|
| GET | /pwa_splits | List splits |
| GET | /pwa_splits/{id} | One split with flows and domains |
| POST | /pwa_splits | Create a split |
| PUT | /pwa_splits/{id} | Update, start or stop |
| POST | /pwa_splits/{id}/clone | Clone |
| DELETE | /pwa_splits/{id} | Archive |
Replace YOUR_KEY and panel.example.com with your own.
List splits
GET/pwa_splits
Splits of your teams, new and recently changed first. Archived ones are not shown.
| Parameter | What it filters |
|---|---|
search | Part of the name |
domain | Part of a domain attached to the split |
status | active - only running, inactive - only stopped |
archived | 1 - only archived |
team_id | Team |
page, per_page | Page and its size (100 by default) |
curl "https://panel.example.com/api/pwa_splits?status=active" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json"<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->get('pwa_splits', ['query' => ['status' => 'active']]);
$splits = json_decode($response->getBody(), true)['data']['pwa_splits'];
foreach ($splits as $split) {
echo "{$split['id']} {$split['name']}: {$split['pwa_flows_count']} flows" . PHP_EOL;
}# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
response = requests.get(f"{API}/pwa_splits", headers=HEADERS, params={"status": "active"}, timeout=30)
splits = response.json()["data"]["pwa_splits"]
for split in splits:
print(split["id"], split["name"], split["pwa_flows_count"], "flows")// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwa_splits?status=active`, { headers, signal: AbortSignal.timeout(30000) });
const { data } = await response.json();
for (const split of data.pwa_splits) {
console.log(split.id, split.name, split.pwa_flows_count, 'flows');
}Response (shortened)
{
"data": {
"meta": { "current_page": 1, "per_page": 100, "last_page": 1, "total": 1 },
"pwa_splits": [
{
"id": "WQby",
"name": "US test",
"status": "active",
"archived": false,
"filter_stream_id": null,
"pwa_flows_count": 2,
"domains_count": 1,
"stat_uniques": 1520,
"stat_installs": 410,
"stat_registrations": 96,
"stat_deposits": 21,
"stat_repeat_deposits": 7,
"team": { "id": "WZAX", "name": "Team A" },
"pwa_flows": [
{ "id": "ymqy", "weight": 70, "pwa": { "id": "WZAX", "name": "Promo EN", "active": true } },
{ "id": "Xbpy", "weight": 30, "pwa": { "id": "Lm3T", "name": "Promo EN 2", "active": true } }
],
"domains": [ { "id": "Xk9P", "domain": "spin-us.example" } ],
"created_at": "05 Oct 2026 14:22",
"updated_at": "05 Oct 2026 14:22"
}
]
}
}The stat_* counters are the all-time sum over the split's flows. For a split without flows they come as null, not 0.
Only the common errors - see "API introduction".
One split
GET/pwa_splits/{id}
A split with its domains, flows and its own settings in config.
curl https://panel.example.com/api/pwa_splits/WQby \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json"<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->get('pwa_splits/WQby');
$split = json_decode($response->getBody(), true)['data']['pwa_split'];
echo "{$split['name']}: {$split['status']}" . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
response = requests.get(f"{API}/pwa_splits/WQby", headers=HEADERS, timeout=30)
split = response.json()["data"]["pwa_split"]
print(split["name"], split["status"])// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwa_splits/WQby`, { headers, signal: AbortSignal.timeout(30000) });
const { data } = await response.json();
console.log(data.pwa_split.name, data.pwa_split.status);Response (shortened)
{
"data": {
"pwa_split": {
"id": "WQby",
"name": "US test",
"status": "active",
"filter_account_id": null,
"filter_stream_id": null,
"team": { "id": "WZAX", "name": "Team A" },
"domains": [ { "id": "Xk9P", "domain": "spin-us.example" } ],
"pwa_flows": [
{ "id": "ymqy", "weight": 70, "stat_installs": 290, "pwa": { "id": "WZAX", "name": "Promo EN" } },
{ "id": "Xbpy", "weight": 30, "stat_installs": 120, "pwa": { "id": "Lm3T", "name": "Promo EN 2" } }
],
"config": {
"offerLinks": {
"US": [ { "lang": "en", "link": "https://offer.example/?sub1={sub1}" } ]
}
}
}
}
}A split's config can be null - then the settings of the PWAs themselves apply.
While AI weights are on, the response carries ai: shares - each flow's current share in percent (by flow id) and learning - true while some flow has fewer than 100 unique visits. With AI weights off, ai is null.
Errors
| Code | message | When |
|---|---|---|
404 | Not found | Wrong identifier |
403 | This action is unauthorized. | The object belongs to another team |
Creating a split
POST/pwa_splits
| Field | Required | What it is |
|---|---|---|
name | yes | Split name in the panel |
team_id | for team_lead and admin | Team; for a customer it is filled in automatically |
status | no | active - start right away. By default a split is created stopped |
filter_account_id, filter_stream_id | no | Filter account and a stream in it - instead of the PWAs' own filter |
config.offerLinks | no | Own offer links - instead of the PWAs' links, same format as for a PWA |
config.metrics | no | Own Facebook pixel (facebook.pixelId, facebook.events) and Google tag (google.tagId) |
config.ai | no | AI weights: enabled (true/false) and goal - install, registration or deposit (defaults to install) |
curl -X POST https://panel.example.com/api/pwa_splits \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "US test",
"team_id": "WZAX",
"config": {
"offerLinks": { "US": [ { "lang": "en", "link": "https://offer.example/?sub1={sub1}" } ] }
}
}'<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->post('pwa_splits', ['json' => [
'name' => 'US test',
'team_id' => 'WZAX',
'config' => [
'offerLinks' => ['US' => [['lang' => 'en', 'link' => 'https://offer.example/?sub1={sub1}']]],
],
]]);
$split = json_decode($response->getBody(), true)['data']['pwa_split'];
echo "Created split {$split['id']}" . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
response = requests.post(f"{API}/pwa_splits", headers=HEADERS, json={
"name": "US test",
"team_id": "WZAX",
"config": {
"offerLinks": {"US": [{"lang": "en", "link": "https://offer.example/?sub1={sub1}"}]},
},
}, timeout=30)
split = response.json()["data"]["pwa_split"]
print("Created split", split["id"])// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwa_splits`, {
method: 'POST',
headers,
body: JSON.stringify({
name: 'US test',
team_id: 'WZAX',
config: {
offerLinks: { US: [{ lang: 'en', link: 'https://offer.example/?sub1={sub1}' }] },
},
}),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
console.log('Created split', data.pwa_split.id);The response is the created split in data.pwa_split, the same as GET/pwa_splits/{id}: status (inactive unless you passed it), team, empty domains and pwa_flows. Flows are added with the methods of the API section "Flows", domains - with the methods of the API section "Domains".
Errors
| Code | message | When |
|---|---|---|
422 | The team id field is required. | team_id not passed (for team_lead and admin) |
422 | You do not have access to this team | team_id is not your team |
422 | The name field is required. | name not passed |
422 | The selected status is invalid. | status is neither active nor inactive |
422 | The offer links country "USA" must be a two-letter code. | A country in config.offerLinks is not a two-letter code |
422 | The link field format is invalid. | A link in config.offerLinks doesn't start with http:// or https:// |
422 | The selected resource does not exist | pixelId or tagId is not the identifier of your pixel or tag from the API section "Pixels and tags" |
Updating, starting and stopping
PUT/pwa_splits/{id}
Changes only the fields passed. Accepts the same fields as creation. You don't need to pass team_id here - only when moving the split to another team.
Start a split:
curl -X PUT https://panel.example.com/api/pwa_splits/WQby \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->put('pwa_splits/WQby', ['json' => ['status' => 'active']]);
$split = json_decode($response->getBody(), true)['data']['pwa_split'];
echo "{$split['name']}: {$split['status']}" . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
response = requests.put(f"{API}/pwa_splits/WQby", headers=HEADERS, json={"status": "active"}, timeout=30)
split = response.json()["data"]["pwa_split"]
print(split["name"], split["status"])// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwa_splits/WQby`, {
method: 'PUT',
headers,
body: JSON.stringify({ status: 'active' }),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
console.log(data.pwa_split.name, data.pwa_split.status);To stop it - the same with "status": "inactive". Team members get a notification that the split was stopped.
config is replaced as a whole
If the request contains config, it replaces all split settings: pass only metrics and the offer links are erased. First get the current config, change what you need in it and send it back as a whole - just like for a PWA.
The response is the split after the change in data.pwa_split, with flows and domains.
Starting, stopping and new settings reach the traffic within three minutes.
Errors
| Code | message | When |
|---|---|---|
404 | Not found | Wrong identifier |
403 | This action is unauthorized. | The object belongs to another team |
422 | The selected status is invalid. | status is neither active nor inactive |
422 | You do not have access to this team | team_id is not your team |
Errors in config are the same as on creation.
Cloning
POST/pwa_splits/{id}/clone
Makes one copy of the split. No request body is needed.
The copy gets the original's name with Clone appended, its settings, filter and flows with weights. Domains are not copied, and the copy itself is created stopped: rename it, attach domains and start it.
curl -X POST https://panel.example.com/api/pwa_splits/WQby/clone \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json"<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
// 1. The copy gets the name "US test Clone"
$response = $api->post('pwa_splits/WQby/clone');
$clone = json_decode($response->getBody(), true)['data']['pwa_split'];
// 2. Rename it
$api->put("pwa_splits/{$clone['id']}", ['json' => ['name' => 'US test 2']]);
echo "Copy {$clone['id']} renamed" . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
# 1. The copy gets the name "US test Clone"
response = requests.post(f"{API}/pwa_splits/WQby/clone", headers=HEADERS, timeout=30)
clone = response.json()["data"]["pwa_split"]
# 2. Rename it
requests.put(f"{API}/pwa_splits/{clone['id']}", headers=HEADERS, json={"name": "US test 2"}, timeout=30)
print("Copy", clone["id"], "renamed")// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
// 1. The copy gets the name "US test Clone"
const cloned = await fetch(`${API}/pwa_splits/WQby/clone`, { method: 'POST', headers, signal: AbortSignal.timeout(30000) });
const clone = (await cloned.json()).data.pwa_split;
// 2. Rename it
await fetch(`${API}/pwa_splits/${clone.id}`, {
method: 'PUT',
headers,
body: JSON.stringify({ name: 'US test 2' }),
signal: AbortSignal.timeout(30000),
});
console.log('Copy', clone.id, 'renamed');The response is the copy in data.pwa_split, in the same shape as GET/pwa_splits/{id}.
Errors
| Code | message | When |
|---|---|---|
404 | Not found | Wrong identifier |
403 | This action is unauthorized. | The object belongs to another team |
Archiving
DELETE/pwa_splits/{id}
Archives the split together with its flows: the split's domains stop serving traffic, the statistics are kept. Archived splits are shown in the list with archived=1.
curl -X DELETE https://panel.example.com/api/pwa_splits/WQby \
-H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json"<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->delete('pwa_splits/WQby');
echo $response->getStatusCode() === 200 ? 'Split archived' . PHP_EOL : 'Error' . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}
response = requests.delete(f"{API}/pwa_splits/WQby", headers=HEADERS, timeout=30)
print("Split archived" if response.ok else "Error")// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwa_splits/WQby`, { method: 'DELETE', headers, signal: AbortSignal.timeout(30000) });
console.log(response.ok ? 'Split archived' : 'Error');Response: {"data": true}.
Errors
| Code | message | When |
|---|---|---|
404 | Not found | Wrong identifier |
403 | This action is unauthorized. | The object belongs to another team |