Skip to content

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

MethodAddressWhat it does
GET/pwa_splitsList splits
GET/pwa_splits/{id}One split with flows and domains
POST/pwa_splitsCreate a split
PUT/pwa_splits/{id}Update, start or stop
POST/pwa_splits/{id}/cloneClone
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.

ParameterWhat it filters
searchPart of the name
domainPart of a domain attached to the split
statusactive - only running, inactive - only stopped
archived1 - only archived
team_idTeam
page, per_pagePage and its size (100 by default)
bash
curl "https://panel.example.com/api/pwa_splits?status=active" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?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;
}
python
# 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")
js
// 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)
json
{
  "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.

bash
curl https://panel.example.com/api/pwa_splits/WQby \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?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;
python
# 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"])
js
// 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)
json
{
  "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
CodemessageWhen
404Not foundWrong identifier
403This action is unauthorized.The object belongs to another team

Creating a split ​

POST/pwa_splits

FieldRequiredWhat it is
nameyesSplit name in the panel
team_idfor team_lead and adminTeam; for a customer it is filled in automatically
statusnoactive - start right away. By default a split is created stopped
filter_account_id, filter_stream_idnoFilter account and a stream in it - instead of the PWAs' own filter
config.offerLinksnoOwn offer links - instead of the PWAs' links, same format as for a PWA
config.metricsnoOwn Facebook pixel (facebook.pixelId, facebook.events) and Google tag (google.tagId)
config.ainoAI weights: enabled (true/false) and goal - install, registration or deposit (defaults to install)
bash
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
<?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;
python
# 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"])
js
// 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
CodemessageWhen
422The team id field is required.team_id not passed (for team_lead and admin)
422You do not have access to this teamteam_id is not your team
422The name field is required.name not passed
422The selected status is invalid.status is neither active nor inactive
422The offer links country "USA" must be a two-letter code.A country in config.offerLinks is not a two-letter code
422The link field format is invalid.A link in config.offerLinks doesn't start with http:// or https://
422The selected resource does not existpixelId 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:

bash
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
<?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;
python
# 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"])
js
// 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
CodemessageWhen
404Not foundWrong identifier
403This action is unauthorized.The object belongs to another team
422The selected status is invalid.status is neither active nor inactive
422You do not have access to this teamteam_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.

bash
curl -X POST https://panel.example.com/api/pwa_splits/WQby/clone \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?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;
python
# 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")
js
// 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
CodemessageWhen
404Not foundWrong identifier
403This 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.

bash
curl -X DELETE https://panel.example.com/api/pwa_splits/WQby \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?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;
python
# 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")
js
// 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
CodemessageWhen
404Not foundWrong identifier
403This action is unauthorized.The object belongs to another team