PWA
Методы для PWA-приложений: найти нужные, создать новое, поменять настройки, включить или выключить, наклонировать и убрать в архив. Что значат сами настройки, см. раздел «Создание (вкладка «Общие»)» и соседние страницы руководства.
| Метод | Адрес | Что делает |
|---|---|---|
| GET | /pwas | Список PWA |
| GET | /pwas/{id} | Одно PWA со всеми настройками |
| POST | /pwas | Создать PWA |
| PUT | /pwas/{id} | Изменить, включить или выключить |
| POST | /pwas/{id}/clone | Клонировать |
| DELETE | /pwas/{id} | Убрать в архив |
Замените ВАШ_КЛЮЧ и panel.example.com на свои.
Список PWA
GET/pwas
PWA ваших команд, новые и недавно изменённые сверху. Архивные не показываются.
| Параметр | Что фильтрует |
|---|---|
search | Часть названия |
domain | Часть домена, привязанного к PWA |
status | active - только включённые, inactive - только выключенные |
archived | 1 - только архивные |
team_id | Команда |
offer_country | Страна в ссылках на предложения, двухбуквенный код (US, DE); PWA со ссылкой под ANY тоже попадают |
group_id | Группа доступа |
cloudflare_id, filter_account_id | Аккаунт Cloudflare или фильтрации |
page, per_page | Страница и её размер (по умолчанию 100) |
curl "https://panel.example.com/api/pwas?status=active&offer_country=US" \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-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 ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->get('pwas', ['query' => ['status' => 'active', 'offer_country' => 'US']]);
$pwas = json_decode($response->getBody(), true)['data']['pwas'];
foreach ($pwas as $pwa) {
echo "{$pwa['id']} {$pwa['name']}: {$pwa['stat_installs']} установок" . PHP_EOL;
}# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.get(f"{API}/pwas", headers=HEADERS, params={"status": "active", "offer_country": "US"}, timeout=30)
pwas = response.json()["data"]["pwas"]
for pwa in pwas:
print(pwa["id"], pwa["name"], pwa["stat_installs"], "установок")// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas?status=active&offer_country=US`, { headers, signal: AbortSignal.timeout(30000) });
const { data } = await response.json();
for (const pwa of data.pwas) {
console.log(pwa.id, pwa.name, pwa.stat_installs, 'установок');
}Ответ
{
"data": {
"meta": { "current_page": 1, "per_page": 100, "last_page": 1, "total": 1 },
"pwas": [
{
"id": "WZAX",
"name": "Promo EN",
"active": true,
"archived": false,
"filter_stream_id": null,
"icon_url": "/api/pwas/WZAX/assets/icon-96.png",
"stat_uniques": 1520,
"stat_installs": 410,
"stat_registrations": 96,
"stat_deposits": 21,
"stat_repeat_deposits": 7,
"similar_count": 0,
"team": { "id": "WQby", "name": "Team A" },
"user": { "id": "WZAX", "name": "Buyer", "email": "[email protected]" },
"domains": [ { "id": "Xk9P", "domain": "fit-tracker.example" } ],
"groups": [],
"created_at": "05 Oct 2026 14:03",
"updated_at": "05 Oct 2026 14:03"
}
]
}
}Счётчики stat_* - за всё время: уникальные посетители, установки, регистрации, депозиты и повторные депозиты. Статистику за период берите из раздела «Статистика».
Ошибки
| Код | message | Когда |
|---|---|---|
422 | The offer country field format is invalid. | offer_country - не две латинские буквы |
Одно PWA
GET/pwas/{id}
Всё о приложении, включая настройки в поле config.
curl https://panel.example.com/api/pwas/WZAX \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-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 ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->get('pwas/WZAX');
$pwa = json_decode($response->getBody(), true)['data']['pwa'];
echo $pwa['name'] . ': ' . ($pwa['active'] ? 'включено' : 'выключено') . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.get(f"{API}/pwas/WZAX", headers=HEADERS, timeout=30)
pwa = response.json()["data"]["pwa"]
print(pwa["name"], "включено" if pwa["active"] else "выключено")// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas/WZAX`, { headers, signal: AbortSignal.timeout(30000) });
const { data } = await response.json();
console.log(data.pwa.name, data.pwa.active ? 'включено' : 'выключено');Ответ (сокращён)
{
"data": {
"pwa": {
"id": "WZAX",
"name": "Promo EN",
"active": true,
"cloudflare_id": "WZAX",
"filter_account_id": null,
"filter_stream_id": null,
"domains": [ { "id": "Xk9P", "domain": "fit-tracker.example" } ],
"config": {
"general": { "name": "Fit Tracker", "language": "en", "templateAndroid": "google-v1" },
"offerLinks": {
"US": [ { "lang": "en", "link": "https://offer.example/?sub1={sub1}" } ]
},
"metrics": { "facebook": { "pixelId": null, "events": [] }, "google": { "tagId": null } },
"comments": [],
"translations": []
},
"similar": []
}
}
}Поля config повторяют вкладки формы PWA: general - «Общие», offerLinks - ссылки на предложения по странам, metrics - пиксели, comments - отзывы, translations - переводы.
В offerLinks страна ANY - запасная: её ссылку получает посетитель, для которого не нашлось ссылки в его стране (страны нет в списке, не определилась или в ней нет ссылки под его язык); язык * - любой. Изменение PWA с config требует хотя бы одну ссылку, а при general.white.type = off - ссылку под ANY.
Ошибки
| Код | message | Когда |
|---|---|---|
403 | This action is unauthorized. | Нет доступа к PWA: оно не из ваших команд (у customer - создано не им) и не открыто вам через группу доступа |
404 | Not found | PWA с таким id нет |
Создание PWA
POST/pwas
| Поле | Обязательно | Что это |
|---|---|---|
name | да | Название PWA в панели |
cloudflare_id | да | Аккаунт Cloudflare, список - GET/cloudflares |
team_id | для team_lead и admin | Команда: id из GET/teams (раздел «Справочники») или team.id любой строки GET/pwas; у customer подставляется его команда, переданное значение не учитывается |
active | нет | true - сразу включить. По умолчанию PWA создаётся выключенным |
filter_account_id, filter_stream_id | нет | Аккаунт фильтрации и поток в нём |
config | нет | Настройки, как в ответе GET/pwas/{id} |
curl -X POST https://panel.example.com/api/pwas \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"name": "Promo EN",
"team_id": "WQby",
"cloudflare_id": "WZAX",
"config": {
"general": { "name": "Fit Tracker", "language": "en", "templateAndroid": "google-v1" },
"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 ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->post('pwas', ['json' => [
'name' => 'Promo EN',
'team_id' => 'WQby',
'cloudflare_id' => 'WZAX',
'config' => [
'general' => ['name' => 'Fit Tracker', 'language' => 'en', 'templateAndroid' => 'google-v1'],
'offerLinks' => ['US' => [['lang' => 'en', 'link' => 'https://offer.example/?sub1={sub1}']]],
],
]]);
$pwa = json_decode($response->getBody(), true)['data']['pwa'];
echo "Создано PWA {$pwa['id']}" . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.post(f"{API}/pwas", headers=HEADERS, json={
"name": "Promo EN",
"team_id": "WQby",
"cloudflare_id": "WZAX",
"config": {
"general": {"name": "Fit Tracker", "language": "en", "templateAndroid": "google-v1"},
"offerLinks": {"US": [{"lang": "en", "link": "https://offer.example/?sub1={sub1}"}]},
},
}, timeout=30)
pwa = response.json()["data"]["pwa"]
print("Создано PWA", pwa["id"])// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas`, {
method: 'POST',
headers,
body: JSON.stringify({
name: 'Promo EN',
team_id: 'WQby',
cloudflare_id: 'WZAX',
config: {
general: { name: 'Fit Tracker', language: 'en', templateAndroid: 'google-v1' },
offerLinks: { US: [{ lang: 'en', link: 'https://offer.example/?sub1={sub1}' }] },
},
}),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
console.log('Создано PWA', data.pwa.id);Ответ - созданное PWA в data.pwa, с active, archived, team, user и пустым domains. Домен к PWA привязывается отдельно, см. раздел «Домены».
Ошибки
| Код | message | Когда |
|---|---|---|
422 | The name field is required. (and 2 more errors) | Нет name, cloudflare_id или team_id - в errors указано, каких именно |
422 | You do not have access to this team | team_id - команда, в которой вас нет |
422 | The cloudflare id field must be an integer. | cloudflare_id не найден - передан неверный id |
422 | The offer links country "USA" must be a two-letter code or ANY. | Страна в config.offerLinks - не двухбуквенный код и не ANY |
422 | The link field format is invalid. | Ссылка на предложение не начинается с http:// или https:// |
422 | The selected template android is invalid. | Неизвестный шаблон в config.general.templateAndroid / templateIos |
Проще клонировать
Настроек у PWA много, и собирать config с нуля долго. Обычно удобнее настроить одно PWA в панели, а новые делать его клонами.
Изменение, включение и выключение
PUT/pwas/{id}
Меняет только переданные поля. Принимает те же поля, что и создание.
team_id при изменении не обязателен: без него PWA остаётся в своей команде, а передать его нужно только чтобы перенести PWA в другую команду (team_lead - в одну из своих). customer меняет только свои PWA и открытые ему через группу доступа.
Включить PWA:
curl -X PUT https://panel.example.com/api/pwas/WZAX \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"active": true}'<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->put('pwas/WZAX', ['json' => ['active' => true]]);
$pwa = json_decode($response->getBody(), true)['data']['pwa'];
echo $pwa['name'] . ': ' . ($pwa['active'] ? 'включено' : 'выключено') . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.put(f"{API}/pwas/WZAX", headers=HEADERS, json={"active": True}, timeout=30)
pwa = response.json()["data"]["pwa"]
print(pwa["name"], "включено" if pwa["active"] else "выключено")// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas/WZAX`, {
method: 'PUT',
headers,
body: JSON.stringify({ active: true }),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
console.log(data.pwa.name, data.pwa.active ? 'включено' : 'выключено');Выключить - то же самое с "active": false. Участники команды получат уведомление, что PWA остановлено.
config заменяется целиком
Если в запросе есть config, он заменяет все настройки PWA, а не дополняет их: всё, чего нет в запросе, сотрётся. Поэтому сначала получите текущий config, поменяйте в нём нужное и отправьте обратно целиком.
Поменять ссылку на предложение для США:
# 1. Сохраните текущее PWA
curl https://panel.example.com/api/pwas/WZAX \
-H "Authorization: Bearer ВАШ_КЛЮЧ" -H "Accept: application/json" > pwa.json
# 2. Поменяйте ссылку и отправьте config целиком (нужен jq)
jq '{config: (.data.pwa.config
| .offerLinks.US = [{"lang": "en", "link": "https://new-offer.example/?sub1={sub1}"}])}' pwa.json \
| curl -X PUT https://panel.example.com/api/pwas/WZAX \
-H "Authorization: Bearer ВАШ_КЛЮЧ" -H "Accept: application/json" \
-H "Content-Type: application/json" -d @-<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
// 1. Текущее PWA
$pwa = json_decode($api->get('pwas/WZAX')->getBody(), true)['data']['pwa'];
// 2. Меняем ссылку для США, остальной config остаётся как был
$config = $pwa['config'];
$config['offerLinks']['US'] = [['lang' => 'en', 'link' => 'https://new-offer.example/?sub1={sub1}']];
// 3. Отправляем config целиком
$response = $api->put('pwas/WZAX', ['json' => ['config' => $config]]);
$updated = json_decode($response->getBody(), true)['data']['pwa'];
echo $updated['config']['offerLinks']['US'][0]['link'] . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
# 1. Текущее PWA
pwa = requests.get(f"{API}/pwas/WZAX", headers=HEADERS, timeout=30).json()["data"]["pwa"]
# 2. Меняем ссылку для США, остальной config остаётся как был
config = pwa["config"]
config["offerLinks"]["US"] = [{"lang": "en", "link": "https://new-offer.example/?sub1={sub1}"}]
# 3. Отправляем config целиком
response = requests.put(f"{API}/pwas/WZAX", headers=HEADERS, json={"config": config}, timeout=30)
updated = response.json()["data"]["pwa"]
print(updated["config"]["offerLinks"]["US"][0]["link"])// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
// 1. Текущее PWA
const current = await fetch(`${API}/pwas/WZAX`, { headers, signal: AbortSignal.timeout(30000) });
const { pwa } = (await current.json()).data;
// 2. Меняем ссылку для США, остальной config остаётся как был
const config = pwa.config;
config.offerLinks.US = [{ lang: 'en', link: 'https://new-offer.example/?sub1={sub1}' }];
// 3. Отправляем config целиком
const response = await fetch(`${API}/pwas/WZAX`, {
method: 'PUT',
headers,
body: JSON.stringify({ config }),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
console.log(data.pwa.config.offerLinks.US[0].link);Ответ - PWA после изменения в data.pwa.
Ошибки
| Код | message | Когда |
|---|---|---|
422 | You do not have access to this team | team_id - команда, в которой вас нет |
422 | At least one offer link is required. | В config.offerLinks нет ни одной ссылки |
422 | The white page is off - at least one offer link with country ANY is required. | general.white.type = off, а ссылки под ANY нет |
403 | This action is unauthorized. | Нет доступа к PWA: оно не из ваших команд (у customer - создано не им) и не открыто вам через группу доступа |
Ошибки в config - те же, что при создании.
Клонирование
POST/pwas/{id}/clone
Делает копии PWA, по одной на каждое название в names.
Копия получает настройки, картинки, аккаунты Cloudflare и фильтрации и группы оригинала. Не копируются домены и поток фильтрации, а сама копия создаётся выключенной: привяжите домен, задайте поток и включите её.
curl -X POST https://panel.example.com/api/pwas/WZAX/clone \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"names": ["Promo EN 2", "Promo EN 3"]}'<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->post('pwas/WZAX/clone', ['json' => ['names' => ['Promo EN 2', 'Promo EN 3']]]);
$clones = json_decode($response->getBody(), true)['data']['clones'];
foreach ($clones as $clone) {
echo "{$clone['id']} {$clone['name']}" . PHP_EOL;
}# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.post(f"{API}/pwas/WZAX/clone", headers=HEADERS, json={"names": ["Promo EN 2", "Promo EN 3"]}, timeout=30)
clones = response.json()["data"]["clones"]
for clone in clones:
print(clone["id"], clone["name"])// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas/WZAX/clone`, {
method: 'POST',
headers,
body: JSON.stringify({ names: ['Promo EN 2', 'Promo EN 3'] }),
signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();
for (const clone of data.clones) {
console.log(clone.id, clone.name);
}Ответ
{
"data": {
"clones": [
{ "id": "WQby", "name": "Promo EN 2", "domains": [] },
{ "id": "Lm3T", "name": "Promo EN 3", "domains": [] }
]
}
}Ошибки
| Код | message | Когда |
|---|---|---|
422 | The names field is required. | Нет names или это пустой список |
403 | This action is unauthorized. | Нет доступа к PWA: оно не из ваших команд (у customer - создано не им) и не открыто вам через группу доступа |
Архивирование
DELETE/pwas/{id}
Убирает PWA в архив вместе с его потоками: приложение перестаёт открываться, но статистика сохраняется. Архивные PWA видны в списке с archived=1.
curl -X DELETE https://panel.example.com/api/pwas/WZAX \
-H "Authorization: Bearer ВАШ_КЛЮЧ" \
-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 ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$response = $api->delete('pwas/WZAX');
echo $response->getStatusCode() === 200 ? 'PWA в архиве' . PHP_EOL : 'Ошибка' . PHP_EOL;# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
response = requests.delete(f"{API}/pwas/WZAX", headers=HEADERS, timeout=30)
print("PWA в архиве" if response.ok else "Ошибка")// Node.js 18+, без зависимостей; сохраните как script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const response = await fetch(`${API}/pwas/WZAX`, { method: 'DELETE', headers, signal: AbortSignal.timeout(30000) });
console.log(response.ok ? 'PWA в архиве' : 'Ошибка');Ответ: {"data": true}.
Ошибки
| Код | message | Когда |
|---|---|---|
403 | This action is unauthorized. | Нет доступа к PWA: оно не из ваших команд (у customer - создано не им) и не открыто вам через группу доступа |