Skip to content

Pushes ​

Methods for push notifications: create a push, change its texts and schedule, find out the audience size, send it right away, clone it and archive it. For how pushes, types, schedule and filter work, see the "Push notifications" section.

MethodAddressWhat it does
GET/pwa_pushesList pushes
GET/pwa_pushes/{id}One push
POST/pwa_pushesCreate a push
PUT/pwa_pushes/{id}Update, turn on, turn off, restore from archive
GET/pwa_pushes/{id}/audienceHow many subscribers get the push
POST/pwa_pushes/{id}/sendSend now
POST/pwa_pushes/{id}/cloneClone
DELETE/pwa_pushes/{id}Archive
POST/pwa_pushes/previewShow how a text with variants reaches a subscriber
POST/pwa_pushes/uniqueizeRewrite a text into variants with AI

Replace YOUR_KEY and panel.example.com with your own.

List pushes ​

GET/pwa_pushes

Pushes of your teams, recently changed first. Archived ones are not shown.

ParameterWhat it filters
searchPart of the name
typemanual, scheduled or triggered
statusactive or inactive
team_idTeam
pwa_idPushes that reach this PWA's subscribers
archived1 - including archived
page, per_pagePage and its size (100 by default)
bash
curl "https://panel.example.com/api/pwa_pushes?status=active&pwa_id=WZAX" \
  -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_pushes', ['query' => ['status' => 'active', 'pwa_id' => 'WZAX']]);
$pushes = json_decode($response->getBody(), true)['data']['pwa_pushes'];

foreach ($pushes as $push) {
    echo "{$push['name']}: sent {$push['sent_count']}, clicks {$push['clicked_count']}\n";
}
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_pushes", headers=HEADERS, params={"status": "active", "pwa_id": "WZAX"}, timeout=30)
pushes = response.json()["data"]["pwa_pushes"]

for push in pushes:
    print(push["name"], "sent", push["sent_count"], "clicks", push["clicked_count"])
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 params = new URLSearchParams({ status: 'active', pwa_id: 'WZAX' });
const response = await fetch(`${API}/pwa_pushes?${params}`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

for (const push of data.pwa_pushes) {
  console.log(push.name, 'sent', push.sent_count, 'clicks', push.clicked_count);
}
Response
json
{
  "data": {
    "meta": { "current_page": 1, "per_page": 100, "last_page": 1, "total": 1 },
    "pwa_pushes": [
      {
        "id": "WZAX",
        "name": "Evening reminder",
        "type": "scheduled",
        "status": "active",
        "default_lang": "en",
        "config": {
          "messages": {
            "en": { "title": "{Hey|Hi}, your plan for today is ready", "body": "Just 15 minutes - start now", "url": null, "icon": null }
          },
          "schedule": { "slots": [ { "days": [1, 2, 3, 4, 5], "time": "19:00" } ] },
          "filter": { "pwa_ids": ["WZAX"], "countries": ["US"], "statuses": ["installed"] }
        },
        "sent_count": 1250,
        "failed_count": 14,
        "clicked_count": 96,
        "archived": false,
        "last_run_at": "2026-10-05T19:00:00.000000Z",
        "team": { "id": "WQby", "name": "Team A" },
        "created_at": "05 Oct 2026 14:23",
        "updated_at": "05 Oct 2026 14:23"
      }
    ]
  }
}

All-time counters: sent_count - delivered, failed_count - not delivered, clicked_count - clicks. last_run_at is when the push was last sent; unlike the other dates it comes in ISO 8601 format.

Errors
CodemessageWhen
422The selected type is invalid.type is not one of manual, scheduled, triggered
422The selected status is invalid.status is neither active nor inactive

One push ​

GET/pwa_pushes/{id}

The same object as in the list, in data.pwa_push.

bash
curl https://panel.example.com/api/pwa_pushes/WZAX \
  -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,
]);

$push = json_decode($api->get('pwa_pushes/WZAX')->getBody(), true)['data']['pwa_push'];

echo "{$push['name']}: {$push['type']}, {$push['status']}" . PHP_EOL;
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

push = requests.get(f"{API}/pwa_pushes/WZAX", headers=HEADERS, timeout=30).json()["data"]["pwa_push"]

print(push["name"], push["type"], push["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_pushes/WZAX`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log(data.pwa_push.name, data.pwa_push.type, data.pwa_push.status);
Errors
CodemessageWhen
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Creating a push ​

POST/pwa_pushes

FieldRequiredWhat it is
nameyesPush name in the panel
team_idyesThe push's team
typeyesmanual - only via send, scheduled - on a schedule, triggered - some time after a subscriber's event
default_langyesThe language whose text subscribers without their own translation get
statusnoactive - works on schedule and triggers. inactive by default
config.messagesnoTexts by language: the key is a language code, inside title (required, up to 120 characters), body (up to 500), url, icon
config.schedule.slotsnoSlots: days - weekdays from 1 (Mon) to 7 (Sun), time - time HH:MM in UTC. Without slots a scheduled push is saved but never sent
config.schedule.delay_afternoDelays after install, registration, deposit: 30m, 2h, 3d; null - the trigger is off. Without delays a triggered push is never sent
config.filternoWho to send to: pwa_ids - PWAs, countries - two-letter country codes, statuses - installed, registered, deposited

For a customer, team_id is filled in automatically - their own team.

An empty filter means "all subscribers of all the team's PWAs". pwa_ids may list only PWAs of the push's team that you have access to, otherwise 422. Subscribers of disabled and archived PWAs are not in the audience: their apps no longer open.

In title and body you can write text variants - {Hey|Hi} and the other constructs from the "Push notifications" section: each subscriber gets their own combination.

bash
curl -X POST https://panel.example.com/api/pwa_pushes \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Evening reminder",
    "team_id": "WQby",
    "type": "scheduled",
    "default_lang": "en",
    "status": "active",
    "config": {
      "messages": { "en": { "title": "{Hey|Hi}, your plan for today is ready", "body": "Just 15 minutes - start now" } },
      "schedule": { "slots": [ { "days": [1, 2, 3, 4, 5], "time": "19:00" } ] },
      "filter": { "pwa_ids": ["WZAX"], "countries": ["US"], "statuses": ["installed"] }
    }
  }'
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_pushes', ['json' => [
    'name' => 'Evening reminder',
    'team_id' => 'WQby',
    'type' => 'scheduled',
    'default_lang' => 'en',
    'status' => 'active',
    'config' => [
        'messages' => ['en' => ['title' => '{Hey|Hi}, your plan for today is ready', 'body' => 'Just 15 minutes - start now']],
        'schedule' => ['slots' => [['days' => [1, 2, 3, 4, 5], 'time' => '19:00']]],
        'filter' => ['pwa_ids' => ['WZAX'], 'countries' => ['US'], 'statuses' => ['installed']],
    ],
]]);
$pushId = json_decode($response->getBody(), true)['data']['pwa_push']['id'];

echo "Created push {$pushId}" . 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_pushes", headers=HEADERS, json={
    "name": "Evening reminder",
    "team_id": "WQby",
    "type": "scheduled",
    "default_lang": "en",
    "status": "active",
    "config": {
        "messages": {"en": {"title": "{Hey|Hi}, your plan for today is ready", "body": "Just 15 minutes - start now"}},
        "schedule": {"slots": [{"days": [1, 2, 3, 4, 5], "time": "19:00"}]},
        "filter": {"pwa_ids": ["WZAX"], "countries": ["US"], "statuses": ["installed"]},
    },
}, timeout=30)
push_id = response.json()["data"]["pwa_push"]["id"]

print("Created push", push_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_pushes`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    name: 'Evening reminder',
    team_id: 'WQby',
    type: 'scheduled',
    default_lang: 'en',
    status: 'active',
    config: {
      messages: { en: { title: '{Hey|Hi}, your plan for today is ready', body: 'Just 15 minutes - start now' } },
      schedule: { slots: [{ days: [1, 2, 3, 4, 5], time: '19:00' }] },
      filter: { pwa_ids: ['WZAX'], countries: ['US'], statuses: ['installed'] },
    },
  }),
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log('Created push', data.pwa_push.id);

The response is the created push in data.pwa_push, the same as GET/pwa_pushes/{id}. A push with the active status starts sending at the nearest matching slot or on a trigger - no separate start is needed.

Errors
CodemessageWhen
422The name field is required.A required field is missing - the same for team_id, type, default_lang
422You do not have access to this teamteam_id of a team you are not in
422The selected type is invalid.type is not one of manual, scheduled, triggered
422Every message needs a title.The text in some language has no title
422The config.schedule.slots.0.time field must match the format H:i.The slot time is not in HH:MM format
422The config.schedule.slots.0.days.0 field must be between 1 and 7.A weekday outside 1-7
422The delay must look like 30m, 2h or 3d.A trigger delay in another format
422The selected PWA does not exist.A non-existent PWA in config.filter.pwa_ids
422You do not have access to this resourceA PWA in config.filter.pwa_ids you have no access to
422This resource belongs to another teamA PWA of another team in config.filter.pwa_ids
422The config.filter.countries.0 field must be 2 characters.A country not given as a two-letter code

Updating, turning on and off ​

PUT/pwa_pushes/{id}

Changes only the fields passed, accepts the same fields as creation. To stop a push - "status": "inactive", to restore it from the archive - "archived": false.

bash
curl -X PUT https://panel.example.com/api/pwa_pushes/WZAX \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"status": "inactive"}'
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_pushes/WZAX', ['json' => ['status' => 'inactive']]);
$push = json_decode($response->getBody(), true)['data']['pwa_push'];

echo "{$push['name']}: {$push['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_pushes/WZAX", headers=HEADERS, json={"status": "inactive"}, timeout=30)
push = response.json()["data"]["pwa_push"]

print(push["name"], push["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_pushes/WZAX`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({ status: 'inactive' }),
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log(data.pwa_push.name, data.pwa_push.status);

The response is the push after the change in data.pwa_push.

config is replaced as a whole

If the request contains config, it replaces the texts, schedule and filter at once: whatever is missing from the request is erased. First get the push, change what you need in its config and send config as a whole.

Errors
CodemessageWhen
422The selected status is invalid.status is neither active nor inactive
422Every message needs a title.The same config checks as on creation, including the PWAs in config.filter.pwa_ids
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Audience size ​

GET/pwa_pushes/{id}/audience

How many subscribers match the push's filter right now, without subscribers of disabled and archived PWAs. Handy to check before sending.

bash
curl https://panel.example.com/api/pwa_pushes/WZAX/audience \
  -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,
]);

$count = json_decode($api->get('pwa_pushes/WZAX/audience')->getBody(), true)['data']['count'];

echo "Will receive the push: {$count}" . PHP_EOL;
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

count = requests.get(f"{API}/pwa_pushes/WZAX/audience", headers=HEADERS, timeout=30).json()["data"]["count"]

print("Will receive the push:", count)
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_pushes/WZAX/audience`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log('Will receive the push:', data.count);

Response: {"data": {"count": 1250}}.

Errors
CodemessageWhen
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Send now ​

POST/pwa_pushes/{id}/send

Queues the push for sending to the whole audience of its filter right away.

This is a real mailing

The request sends notifications to real subscribers, and sending can't be cancelled. The method works with any push status and type, even a disabled one, and doesn't remember past sends: every call sends the push to everyone again, including those who already got it on the schedule or a trigger. An archived push is not sent (422). At most 10 sends per minute per user. Don't call it in a loop or when retrying after network errors - check audience and the counters first.

bash
curl -X POST https://panel.example.com/api/pwa_pushes/WZAX/send \
  -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,
]);

$queued = json_decode($api->post('pwa_pushes/WZAX/send')->getBody(), true)['data']['queued'];

echo "Queued for sending: {$queued}" . PHP_EOL;
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

queued = requests.post(f"{API}/pwa_pushes/WZAX/send", headers=HEADERS, timeout=30).json()["data"]["queued"]

print("Queued for sending:", queued)
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_pushes/WZAX/send`, {
  method: 'POST',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log('Queued for sending:', data.queued);

Response: {"data": {"queued": 1250}} - how many subscribers were queued. The delivery itself runs in the background, the result shows up in sent_count and failed_count.

Errors
CodemessageWhen
422An archived push cannot be sentThe push is archived
429Too many requests. Try again in a minute.More than 10 sends in a minute
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Cloning ​

POST/pwa_pushes/{id}/clone

Copies of a push, one per name in names. A copy gets the original's team, type, language and the whole config, and is created disabled with zero counters.

bash
curl -X POST https://panel.example.com/api/pwa_pushes/WZAX/clone \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"names": ["Evening reminder DE"]}'
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_pushes/WZAX/clone', ['json' => ['names' => ['Evening reminder DE']]]);
$clones = json_decode($response->getBody(), true)['data']['clones'];

foreach ($clones as $clone) {
    echo "{$clone['id']} {$clone['name']}" . 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_pushes/WZAX/clone", headers=HEADERS, json={"names": ["Evening reminder DE"]}, timeout=30)
clones = response.json()["data"]["clones"]

for clone in clones:
    print(clone["id"], clone["name"])
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_pushes/WZAX/clone`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ names: ['Evening reminder DE'] }),
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

for (const clone of data.clones) {
  console.log(clone.id, clone.name);
}
Response
json
{
  "data": {
    "clones": [
      { "id": "WQby", "name": "Evening reminder DE", "type": "scheduled", "status": "inactive", "default_lang": "en" }
    ]
  }
}
Errors
CodemessageWhen
422The names field is required.names not passed
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Archiving ​

DELETE/pwa_pushes/{id}

Archives the push: it is no longer sent on the schedule and triggers, the history and counters are kept. To restore it - PUT with "archived": false.

bash
curl -X DELETE https://panel.example.com/api/pwa_pushes/WZAX \
  -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_pushes/WZAX');

echo 'Push archived, HTTP ' . $response->getStatusCode() . 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_pushes/WZAX", headers=HEADERS, timeout=30)

print("Push archived, HTTP", response.status_code)
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_pushes/WZAX`, {
  method: 'DELETE',
  headers,
  signal: AbortSignal.timeout(30000),
});

console.log('Push archived, HTTP', response.status);

Response: {"data": true}.

Errors
CodemessageWhen
404Not foundNo push with this id
403This action is unauthorized.A push of another team or created by another user (for a customer)

Text preview ​

POST/pwa_pushes/preview

Builds one random combination from a text with variants, the way a subscriber will see it. The text field - up to 2000 characters. Each call gives a new combination.

The push title and text can be rendered in one call: instead of text, send title and body (up to 2000 characters each) - the answer is {"data": {"title": …, "body": …}}, an empty field comes back as an empty string.

bash
curl -X POST https://panel.example.com/api/pwa_pushes/preview \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"text": "{Hey|Hi}, {%RINT=(3,9)} new lessons"}'
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_pushes/preview', ['json' => ['text' => '{Hey|Hi}, {%RINT=(3,9)} new lessons']]);

echo json_decode($response->getBody(), true)['data']['text'] . 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_pushes/preview", headers=HEADERS, json={"text": "{Hey|Hi}, {%RINT=(3,9)} new lessons"}, timeout=30)
print(response.json()["data"]["text"])
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_pushes/preview`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ text: '{Hey|Hi}, {%RINT=(3,9)} new lessons' }),
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log(data.text);

Response: {"data": {"text": "Hey, 7 new lessons"}}.

Errors
CodemessageWhen
422The text field is required when none of title / body are present.Neither text nor title / body passed

Uniqueize with AI ​

POST/pwa_pushes/uniqueize

Rewrites a plain phrase into a text with variants, like the Uniqueize with AI button in the push form. Fields: text - a phrase up to 2000 characters, lang - language code (optional).

Works through an AI provider

The method is available only if an administrator connected an AI provider in the panel settings, and every call uses up its limits. Without a provider you get 422 with AI uniqueizer is not configured, if the provider didn't answer - 422 with AI uniqueizer is unavailable.

bash
curl -X POST https://panel.example.com/api/pwa_pushes/uniqueize \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"text": "Your plan is ready", "lang": "en"}'
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' => 60,
]);

$response = $api->post('pwa_pushes/uniqueize', ['json' => ['text' => 'Your plan is ready', 'lang' => 'en']]);
$template = json_decode($response->getBody(), true)['data']['text'];

echo $template . 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_pushes/uniqueize", headers=HEADERS, json={"text": "Your plan is ready", "lang": "en"}, timeout=60)
template = response.json()["data"]["text"]

print(template)
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' };

// AI answers slower than usual - 60-second timeout
const response = await fetch(`${API}/pwa_pushes/uniqueize`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ text: 'Your plan is ready', lang: 'en' }),
  signal: AbortSignal.timeout(60000),
});
const { data } = await response.json();

console.log(data.text);

Response: {"data": {"text": "{Your|The} plan {is ready|is waiting} {for you|}"}}.

Errors
CodemessageWhen
422AI uniqueizer is not configuredNo AI provider connected in the panel
422The text field is required.text not passed