# Push.Express Documentation

Hello! This is Push.Express Documentation start page.

![](https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2FJ10qttXxavStHs8k2Fvl%2FCaptura%20de%20ecra%CC%83%202024-07-19%2C%20a%CC%80s%2018.19.23.png?alt=media\&token=5e472670-25bb-4485-bdae-840189d5a5e3)


# How to use

**PushExpress -- Quckstart How to use**

* [Creating an application](#creating-an-application)
* [Applications](#applications)
* [Send notifications](#send-notifications)
* [Analytics](#analytics)
* [Segments](#segments)

### Creating an application

1. [Add new app.](https://app.push.express/app/)

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4be1cca928f93eb11689dc6a0c7357e01d6a54e4%2Fpush-express-app-1.png?alt=media" alt="" width="800"></div>

<sup>*Create new application*</sup>

> Choose the appropriate type of application.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-825460f973fd2fd22b0c59d93d8222bc111b91e7%2Ftypesofapps.png?alt=media" alt="" width="800"></div>

<sup>*Types of applications*</sup>

2. [Check the documentation.](https://docs.push.express)

> Each SDK has a button that links to the documentation for your convenience.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-5a2d8a2631f412f622fa25a10b0a9a4bae99f538%2Fbuttondocumentation.png?alt=media" alt="" width="800"></div>

<sup>*Quickstart Guide button*</sup>

### Applications

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-0fd3da36c53aae6d242db7a138272251610d6d0d%2Fapplications_overview.png?alt=media" alt="" width="800"></div>

<sup>*Applications menu*</sup>

1. [Applications list.](https://app.push.express/)

> Get an overview of all your applications.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-8b03edf349e3ee161b158289f1286087b2fdc0ab%2Fapplications_applications_list.png?alt=media" alt="" width="800"></div>

<sup>*Applications list page*</sup>

2. [Application groups.](https://app.push.express/app/group/)

> Create app groups by verticals, such as Gambling, Betting, Dating, and Finance. This way, you can send push notifications to different verticals using a single Push.Express account.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-dd74909b53b28f96daf2f0463c320aadef504bda%2Fnewgroup.png?alt=media" alt="" width="800"></div>

<sup>*Grouping applications menu*</sup>

### Send notifications

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-c08c46ac0b3cacf2b3bb2e3c21a27cbb220cbbf4%2Fsendtypes.png?alt=media" alt="" width="800"></div>

<sup>*Send notifications menu*</sup>

1. [Send an instant push.](https://app.push.express/push/instant/)
2. [One-time sending in the future.](https://app.push.express/push/one-send/)

> Send a push notification to each user according to their time zone when New Year or Christmas arrives (use Local time feature).This is popular for sending push notifications related to the start time of sports events. For instance, send a push 15 minutes before the Football match starts, 15 minutes after it begins, and during halftime (don't use the local time feature, the sending will be done according to GMT+3).

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-ef9f94e1b5f1eda35f9b81116bdcb728bd4f37ee%2Fsend_notification_onetime_notification_main_menu.png?alt=media" alt="" width="800"></div>

<sup>*One-time sending menu*</sup>

3. [Smart weekly planner.](https://app.push.express/push/smart/)

> Send a push at the same time on the same day of the week every week. You can choose whether or not to use user's local time. Default is GMT +3.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-f00d9d1023b08f51c72416fd479dc6927d53cfe3%2Fsend_notification_smart_weekly_planner.png?alt=media" alt="" width="800"></div>

<sup>*Smart weekly planner menu*</sup>

4. [Auto-push.](https://app.push.express/push/conversions/)

> Send a push notification based on the occurrence (or non-occurrence) of specific events: installation, registration, or deposit. For example: Set up automatic pushes to be sent 5 minutes after installation or 1 hour after installation if the user has not made a deposit.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-cdf34940a7aceb09dc8f04529ce2266baa5331c6%2Fautopushsettings.png?alt=media" alt="" width="800"></div>

<sup>*Automatic event-triggered sending menu*</sup>

### Analytics

<sup>*Analytics menu*</sup>

1. [Sending history.](https://app.push.express/analytics/history/beta/)

> Get a detailed overview of your push notifications. Filter data by country, task type, sending period, and more. Gain insights for A/B testing to identify the most effective push texts and funnels.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-fc5b203e404dc4f5bf8805bef17102594a2fa520%2Fsendinghistory.png?alt=media" alt="" width="800"></div>

<sup>*Sending history menu*</sup>

2. [Devices list.](https://app.push.express/analytics/devices/)

> Make use of devices overview page. Get all the basic data on your devices. Filters are also applicable.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-04253bb502dfb6febba68375472b913895cd22c5%2Fdevices.png?alt=media" alt="" width="800"></div>

<sup>*Devices list menu*</sup>

### Segments

Create custom [segments.](https://app.push.express/postbacks/offers/) For example, divide the audience by offers, creatives, or affiliates, and send pushes to specific segments.

1. [Add segment.](https://app.push.express/postbacks/offers/)

> Name the segment and click Save.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-d76211515a016c0140a5badd18bdd05d294a65c9%2Fnewsegment.png?alt=media" alt="" width="800"></div>

<sup>*Add new segment field*</sup>

2. Send a postback with the installation ID and the segment that needs to be assigned.

> We recommend assigning any convenient installation ID (such as AppsFlyer ID, Keitaro ID, GAID/IDFA, or any custom ID) to the external user ID during the app development stage.\
> You can find your unique postback URL on the [Segments page.](https://app.push.express/postbacks/offers/)

3. Use the predefined postback URLs for registrations and deposits, which you can also find on the [Segments page.](https://app.push.express/postbacks/offers/)
4. Create [Segment Groups.](https://app.push.express/postbacks/group/)


# Custom events

**PushExpress -- Custom events**

* [Creating custom events](#creating-custom-events)
* [Sending an event to the PushExpress](#sending-an-event-to-the-PushExpress)
* [Events in the task settings](#events-in-the-task-settings)
* [Event analytics](#event-analytics)
* [Second, third and subsequent deposits](#second-third-and-subsequent-deposits)

### Creating custom events

[Create an event using the API.](https://docs.push.express/api/custom-events#create-an-custom-events)

### Sending an event to the PushExpress

[Go to the Segments section.](https://app.push.express/postbacks/offers/)

> Get an overview of all your events and segments.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4630486af044a98d5f7c12398840a56abf082a42%2Fsegments_page.png?alt=media" alt="" width="800"></div>

<sup>*Segments page*</sup>

> The "URL for sending postback" column will contain the url for the GET request to add an event to the device. Replace {your installation id} with the ID of the device where the event occurred.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-0532daf8d15f1d24c375b866e047b95a83b040c3%2Fsegments_page_2.png?alt=media" alt="" width="800"></div>

<sup>*URL for sending postback*</sup>

### Events in the task settings

After creating the event, it will become available in the settings of all types of tasks. The Registration and Deposit system events cannot be changed. Custom events will be displayed in the task filters "What event have occurred" and "What event have NOT occurred" after system events.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-40e4b2b954865eb39b8188fc16c94e4c8b214fef%2Ftask_settings_happened.png?alt=media" alt="" width="800"></div>

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-d079b217d509d0a855902713304670f7d2e61c8b%2Ftask_settings_not_happened.png?alt=media" alt="" width="800"></div>

<sup>*Adding an event to a task*</sup>

### Event analytics

You can see the total number of devices on which the event occurred in the [Segments section](https://app.push.express/postbacks/offers/) -> Custom events

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-67ad7803a80e2a8eee468d982db7b7f7d2e3ffdf%2Fsegment_custom_events.png?alt=media" alt="" width="800"></div>

### Second, third and subsequent deposits

The service automatically tracks each deposit made on a device. For every new deposit, the system records an event with its sequential number (first, second, third, etc.) The total count of devices containing these events is available in the [Segments section](https://app.push.express/postbacks/offers/) -> System events

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-35aad699f5cda564c955f154f3fdafd79bb4e82b%2Fsegment_system_events.png?alt=media" alt="" width="800"></div>

You may define task conditions by utilizing these events.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a10d1498f49fc0b4564bc973d209f0aeb2ed4518%2Fsecond_deposit.png?alt=media" alt="" width="800"></div>


# Analytics

**PushExpress -- Quckstart Analytics**

* [Stats rundown](#stats-rundown)
* [Grouping rundown](#grouping-rundown)
* [Other possibilities](#other-possibilities)

### Stats rundown

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-f6ccd771c96235b5307926057f8a7906dc11bb95%2Fsendinghistory.png?alt=media" alt="" width="800"></div>

<sup>*Stats menu*</sup>

1. Sent, Number.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-e12d7fc06c03341d2724d56d3fb22b12cb6dbbae%2Fanalytics-colors-horizontal.png?alt=media" alt="" width="800"></div>

2. Clicks, Number.

The amount of clicks received on your push notification.

3. CTR, %.

Click-Through Rate. For example, if an ad was displayed 1000 times (impressions) and received 50 clicks, the CTR would be 5%.

4. Registrations, Number.

The amout of devices that proceeded to registration after push-notification.

5. Deposits, Number.

The amount of devices that topped up (made a deposit) their balance after push-notification.

6. Unique Devices Covered, Number.

The amout of unique devices covered in this dispatch. Reflects only the amount of devices that actually received push-notification.

7. Push rate, Number.

The number of pushes received on average by a user over a specified period. For example, if 1000 pushes were sent to 200 unique devices, the Push rate would be 5.

8. Unique CTR, %.

The average number of clicks per unique device. For example, if 1000 devices made 2300 clicks, the Push Rate would be 2.3. This means that, on average, each device clicked 2.3 times.

9. CTR Registrations, %.

Shows you percentage of devices that proceeded to registration after they received push-notification.

10. CTR Deposits, %.

Shows you percentage of devices that proceeded to deposit after they received push-notification.

### Grouping rundown

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a7e90429d272478f6639d582a81a6f0020f59ad5%2Fgrouping.png?alt=media" alt="" width="800"></div>

<sup>*Grouping menu*</sup>

1. Country.

Filter by countires.

2. Language.

Filter by language.

3. Segment.

Filter by your custom created segments.

4. Send ID.

Filter by Send ID. This is a unique internal Send ID in the Push.Express system.

5. Application.

Filter by application.

6. Task Type.

Filter by task type. Choose from smart-weekly, one-time send or autopush (event triggered push-notification).

7. Task Name.

Filter by task name.

8. Language Push.

Filter by device language.

9. Period.

Filter by sending period. Choose from Minute, Hour, Day, Week, Month or custom period of Days.

If you need help with using analytics, please contact our [support team.](https://t.me/pushexpress)

### Other possibilities

1. Download the Demo Apps ([PWA](https://demo-sdk.push.express/js/), [Android](https://app.push.express/template/include/app.apk), iOS (coming soon)) from Push.Express and test the analytics directly on your device! Install the app, send a push notification, click on the push, simulate a registration or a deposit. You can view all the statistics on the Analytics page. Log in to a [separate account](https://app.push.express/auth/?logout\&type=testapp) with demo apps..
2. Contact our [support team](https://t.me/pushexpress) if you have any questions or need assistance.


# Demo apps

**PushExpress -- Quckstart Demo apps**

1. [Log in to a separate account with demo apps.](https://app.push.express/auth/?logout\&type=testapp)
2. [Download the Android](https://app.push.express/template/include/app.apk), [PWA](https://demo-sdk.push.express/js/), or iOS (coming soon) app to your device.
3. Get accostomed with event statuses.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-c9c129b19fbf7f4ec585b9394fae4b3d701a3029%2Ftldr-demo-app.png?alt=media" alt="" width="800"></div>

4. Enjoy your welcome push triggered by [Auto-push.](https://app.push.express/push/conversions/)

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-221b14a99d245f53f36859e3b19b7f1b20168efb%2Fdemo-app-welcome-push.png?alt=media" alt="" width="800"></div>

5. [Confirm](https://app.push.express/analytics/devices/) your freshly installed device appeared in the list of devices.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-9a2069550beacf20b436ab8b9242e9d3960581ff%2Fnew-device.png?alt=media" alt="" width="800"></div>

6. Send an [instant push](https://app.push.express/push/instant/) to your device..

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-c7590dc4107aaeac5faf784ecf4da9a611be1b5e%2Finstantpushdemoapp.png?alt=media" alt="" width="800"></div>

7. In the [Analytics](https://app.push.express/analytics/history/beta/), you will see a record that the push has been sent. Hover over colored numbers to get insight about their meaning.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-b26ff0c53c34e22140c96daf3b158211bf4155a1%2Fsendinghistorydemoapp.png?alt=media" alt="" width="800"></div>

8. What's that colors?

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-e12d7fc06c03341d2724d56d3fb22b12cb6dbbae%2Fanalytics-colors-horizontal.png?alt=media" alt="" width="800"></div>

9. Click on the notification on your device, then return to the [Sending history](https://app.push.express/analytics/history/beta/) page and confirm the click was recorded.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-e4b4cbf38325edd714da73f022df419184136f80%2Fdemo-app-click-push.png?alt=media" alt="" width="800"></div>

10. Click "Register" on your device, and you'll see how we instantly process the event and display it on the [Devices](https://app.push.express/analytics/devices/) page. Now your device will receive pushes intended for registered users.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-cf47da40d3a2561e64fe38ecc92942ab38f3c2f1%2Fregister-device.png?alt=media" alt="" width="800"></div>

11. Now you can experiment with the system:
    * Send a push to the Registered audience only, and you'll receive the push.
    * If you send to everyone except the registered users, you won't receive the push.

### Other possibilities

1. [Create custom segments.](https://app.push.express/postbacks/offers/) For example, divide the audience by offers, creatives, or affiliates, and send pushes to specific segments.
2. [Learn more](https://app.push.express/flow/personal/) about sending types.
3. Contact our [support team](https://t.me/pushexpress) if you have any questions or need assistance.


# PWA

**PushExpress -- SDK PWA Native Push**

* [Creating an application in Push Express](#creating-an-application-in-push-express)
* [Add Push Express web sdk in your code](#add-push-express-web-sdk-in-your-code)
* [Example ready code](#example-ready-code)

### Creating an application in Push Express

1. Create new application.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4be1cca928f93eb11689dc6a0c7357e01d6a54e4%2Fpush-express-app-1.png?alt=media" alt="" width="800"></div>

2. Choose type of application PWA/Web.
3. After creating the application, push\_express\_app\_id will be available.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-007765651b8a85fefcdf5638d910a146c0dd6d67%2Fpush-express-app-5.png?alt=media" alt="" width="800"></div>

4. The VAPID KEY will also be available in application settings. You will need it when integrating the sdk in the app.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-9f4c5ae1ed3be7e7bb02abe6766a5d3309f95c46%2Fvapidkey.png?alt=media" alt="" width="800"></div>

### Add Push Express web sdk in your code

2. Add PushExpress library below

```js
<script src="https://sdk.push.express/js/v2.0.3/push-express-sdk.js"></script>
```

3. Perform the SDK initialization

```js
  if (window.matchMedia('(display-mode: standalone)').matches){
        async function redirectUrl() {
            await initPushExpress({
                appID: "32809-2221",
                vapidKey: "BNIh3BEkoOIt15NJhbcf0XptMQOZF29WwdRRehtb8k8WbvY9544R5yURPeX3-jN_hQbrg-KYH8-2FGeOfFs9rdQ",
                externalID: "ec8c1a88-8d29-40bb-bc80-0bc9c0bd88c0",
                tags: {
                    adID: "",
                    segment: "test-segment",
                    webmaster: "test-webmaster"
                }
            });
            location.href = "https://www.google.com/";
        }
        redirectUrl();
    }
```

4. Initialization function contains the following required settings - appID, vapidKey. As well as optional externalID and tags fields.

```js
  appID: "32809-2221",
```

appID required field is located on the application page in the Push Express service ([look here](#push-express-app-id)).

```js
  vapidKey: "BNIh3BEkoOIt15NJhbcf0XptMQOZF29WwdRRehtb8k8WbvY9544R5yURPeX3-jN_hQbrg-KYH8-2FGeOfFs9rdQ",
```

The vapidKey is located in your application settings ( [look here](#firebase-vapid-key)).

```js
externalID : "ec8c1a88-8d29-40bb-bc80-0bc9c0bd88c0",
```

Send the unique installation id here.

```js
redirectDeeplink: true,
```

If a push notification contains a link, then clicking on the notification will launch the PWA application, and the link will be passed in the deeplink GET parameter. If the redirectDeeplink: true parameter is set, a redirect to this link will be performed. To cancel the redirect to the link, set the value to false.

```js
tags: {
        audiences: "test-segment",
    }
```

The segment code. Use it to add a device to a segment. Segments can be created in the [segments section](https://app.push.express/postbacks/offers/) of your personal account.

```js
tags: {
        webmaster: "test-webmaster",
    }
```

To transfer devices to other users of the service, use the functionality of webmasters. You can add a webmaster in the [personal section](https://app.push.express/settings/personal/) of your personal account.

4. Example code index.html

```js
 <!DOCTYPE html>
 <html lang="en">
     <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <link rel="stylesheet" href="src/style.css">
     </head>
     <body>
         <h1 id="header"></h1>
     </body>
 </html>
 <script src="https://sdk.push.express/js/v2.0.3/push-express-sdk.js"></script>
 <script>
      if (window.matchMedia('(display-mode: standalone)').matches){
      async function redirectUrl() {
         await initPushExpress({
              appID: "32809-2221",
              vapidKey: "BNIh3BEkoOIt15NJhbcf0XptMQOZF29WwdRRehtb8k8WbvY9544R5yURPeX3-jN_hQbrg-KYH8-2FGeOfFs9rdQ",
              externalID: "ec8c1a88-8d29-40bb-bc80-0bc9c0bd88c0",
              redirectDeeplink: true,
              tags: {
                  adID: "",
                  segment: "test-segment",
                  webmaster: "test-webmaster"
              }
          });
          location.href = "https://www.google.com/";
      }
      redirectUrl();
  }
  </script>
```

5. Create service worker file in your project. The file name must be as follows "push-express-sw\.js".

> \[!IMPORTANT] The service worker file must have a name push-express-sw\.js . Otherwise, the installation of service worker will fail.

6. Import library in service worker.

   ```js
   importScripts('https://sdk.push.express/js/v2.0.3/push-express-sw.js');
   ```
7. Example code push-express-sw\.js

```js
importScripts('https://sdk.push.express/js/v2.0.3/push-express-sw.js');
```

### Example ready code

[Сheck out an example of the finished code >>>](https://playcode.io/2346972)


# Kotlin

### PushExpress -- SDK Kotlin

You will need to integrate your Push.Express account with Firebase.

1. Follow [Firebase Cloud Messaging integration guide](#setup-firebase)
2. [Create application in Push.Express](#setup-pushexpress)
3. [Add sdk in your application](#add-sdk-in-your-application)

### Setup Firebase

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings -> General

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Create new Android app or just download `google-services.json` from existing app

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-5acc3664b8fdd0d781bb4e5f1441913c34b70c05%2Ffcm-apps-settings.png?alt=media" alt="" width="800"></div>

If you need to create new app, just:

* Register it
* Download `google-services.json`
* Press next-next-next =)
*

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-67fb2209df86c839112438844161af0ea3a4ca2d%2Ffcm-new-android-app.png?alt=media" alt="" width="800"></div>

4. Put `google-services.json` to your app dir (like `<project>/app/google-services.json`)
5. Add the plugin as a dependency to your project-level build.gradle.kts file: Root-level (project-level) Gradle file (/build.gradle.kts)::

   ```groovy
   // Top-level build file where you can add configuration options common to all sub-projects/modules.
   plugins {
       alias(libs.plugins.android.application) apply false
       alias(libs.plugins.kotlin.android) apply false
       alias(libs.plugins.kotlin.compose) apply false
       // Add the dependency for the Google services Gradle plugin
       id("com.google.gms.google-services") version "4.4.3" apply false
   }
   ```
6. In your **module (app-level)** Gradle file (`<project>/<app-module>/build.gradle.kts`), add the Firebase Cloud Messaging dependency:

   ```groovy
   plugins {
       alias(libs.plugins.android.application)
       alias(libs.plugins.kotlin.android)
       alias(libs.plugins.kotlin.compose)

       id("com.google.gms.google-services")
   }

   dependencies {
       // Import the Firebase BoM
       implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
       implementation("com.google.firebase:firebase-messaging-ktx")
       // ...
   }
   ```

Full official instructions can be found in [Firebase Cloud Messaging Android guide](https://firebase.google.com/docs/android/setup#console).

\
\
\
\ <br>

### Setup Push.Express

#### Get Firebase Private key

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Go to Service accounts, press `Generate new private key` and save it to file `private-key.json` (**you can use same key for all apps**)

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2a00f0365ac888f714f77eef7b0eae1cc1257077%2Ffcm-private-key-page.png?alt=media" alt="" width="800"></div>

#### Integrate your Push.Express App with Firebase

1. Go to your [Push.Express](https://push.express) account
2. Open existing *App* settings or create a new App
3. Switch type application Firebase

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4d4659530a7db5c6d52f24524f829d751c591de6%2Fpx-sdk-switch.png?alt=media" alt="" width="800"></div>
4. Paste `private-key.json` file to *Firebase Admin SDK private key* textbox

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2f39e9dc21f10066a0e59387d28f727cd6345b59%2Fpx-sdk-fcm-key.png?alt=media" alt="" width="800"></div>

\
\
\
\ <br>

### Add sdk in your application

> Before proceeding with the integration, make sure you have already installed the Firebase SDK in your application. If not, follow steps 4-6 in the [Firebase Cloud Messaging integration guide](#setup-firebase)

#### Add the JitPack repository to your build file

Ensure you have the latest Android Studio and Android Gradle Plugin!

In your `settings.gradle.kts`, add the Jitpack repo to repositories list (only if you use Gradle [Centralized Repository Declaration](https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:centralized-repository-declaration) feature, default for new projects since Android Studio Electric Eel):

```groovy
// settings.gradle (Project Settings) in Android Studio
// ...
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // ...
         maven { url = uri("https://jitpack.io") }
    }
}
```

<details>

<summary>Alternatively, if you use old `allprojects` style</summary>

In your **root-level (project-level)** Gradle file (`<project>/build.gradle`), add the Jitpack repo to repositories list:

```groovy
// build.gradle (Project: My_Application) in Android Studio
// ...
allprojects {
    repositories {
        // ...
        maven { url "https://jitpack.io" }
    }
}
```

</details>

#### Add Push.Express SDK dependency

In your **module (app-level)** Gradle file (`<project>/<app-module>/build.gradle`), add the pushexpress-android-sdk dependency:

```groovy
// build.gradle (Module :app) in Android Studio
// ...
dependencies {
    // ...
     implementation ("com.github.pushexpress:pushexpress-android-sdk:v1.4.3")
}
```

#### Add AndroidManifest.xml

To correctly open links from push notifications in the app, add an Intent filter:

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.YourApp">
        
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTask">
            
            <!-- Launcher -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <!-- Deep links with your custom scheme -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="yourapp" android:host="open" />
            </intent-filter>

            <!-- HTTPS URLs for push notifications -->
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="https" android:host="push.express" />
            </intent-filter>

            <!-- Catch all HTTP/HTTPS (for browser redirect) -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="https" />
                <data android:scheme="http" />
            </intent-filter>

            <!-- Fallback for empty deep links -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        
        <!-- Firebase Messaging Service -->
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    </application>
</manifest>
```

You can also set the default values for small icon and large icon in AndroidManifest.xml:

```xml
<meta-data
   android:name="com.pushexpress.sdk.default_notification_icon"
   android:resource="@drawable/ic_small_icon_notification" />

<meta-data
   android:name="com.pushexpress.sdk.default_notification_color"
   android:resource="@color/ic_small_icon_notification_color" />

<meta-data
   android:name="com.pushexpress.sdk.default_notification_large_icon"
   android:resource="@drawable/ic_large_icon_notification" />
```

#### Add required code

1. Get your `PUSHEXPRESS_APP_ID` from [Push.Express](https://push.express) account page

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-1d3d92fe14ced3141ba04aa398a9f7b00ee7b996%2Fpx-sdk-app-id.png?alt=media" alt="" width="800"></div>
2. Create MyFirebaseMessagingService.kt

```kotlin
package com.yourpackage

import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.pushexpress.sdk.main.SdkPushExpress
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch

class MyFirebaseMessagingService : FirebaseMessagingService() {

    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Log.d("FCM", "New Firebase token: $token")

        serviceScope.launch {
            try {
                SdkPushExpress.setFirebaseToken(token)
                Log.d("FCM", "Firebase token successfully set")
            } catch (e: Exception) {
                Log.e("FCM", "Error setting Firebase token: ${e.message}", e)
            }
        }
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        Log.d("FCM", "Message received from: ${remoteMessage.from}")

        if (remoteMessage.data.isNotEmpty()) {
            Log.d("FCM", "Message data: ${remoteMessage.data}")
            try {
                SdkPushExpress.showNotification(remoteMessage.data)
            } catch (e: Exception) {
                Log.e("FCM", "Error showing notification: ${e.message}", e)
            }
        }

        remoteMessage.notification?.let { notification ->
            Log.d("FCM", "Notification title: ${notification.title}")
            Log.d("FCM", "Notification body: ${notification.body}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }
}
```

3. Add code SDK Initialization in MainActivity

```kotlin
package com.com.yourpackage
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import com.google.firebase.messaging.FirebaseMessaging
import com.pushexpress.sdk.main.SdkPushExpress


const val PUSHEXPRESS_APP_ID = "YOUR_APP_ID" // Replace with your App ID
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.d("Myapp", "onCreate called")

        askNotificationPermission()

        if (savedInstanceState == null) {
            getFirebaseTokenAndActivate()
        }

        enableEdgeToEdge()
        setContent {
            KotlinBug3Theme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding),
                        onLogoutClick = { logout() },
                        onLoginClick = { login() }
                    )
                }
            }
        }

        handleIntent(intent)
    }

    private fun getFirebaseTokenAndActivate() {
        FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
            if (task.isSuccessful) {
                val token = task.result
                Log.d("MyApp", "Firebase token: $token")

                CoroutineScope(Dispatchers.IO).launch {
                    SdkPushExpress.setFirebaseToken(token)
                    initializeSdk()

                    Log.d("MyApp", "SDK activated with token")
                }
            } else {
                Log.e("MyApp", "Failed to get Firebase token", task.exception)
            }
        }
    }

    private fun initializeSdk() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.initialize(PUSHEXPRESS_APP_ID)
                SdkPushExpress.setExternalId("some_external_id")
                SdkPushExpress.activate()

                Log.d("Myapp", "App Instance Token: ${SdkPushExpress.getInstanceToken()}")
                Log.d("Myapp", "App External ID: ${SdkPushExpress.getExternalId()}")
            } catch (e: Exception) {
                Log.e("Myapp", "SDK initialization failed", e)
            }
        }
    }


    private val notificationPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            Toast.makeText(this, "Notifications permission granted", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(
                this,
                "FCM can't post notifications without POST_NOTIFICATIONS permission",
                Toast.LENGTH_LONG
            ).show()
        }
    }

    private fun askNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(
                    this,
                    android.Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                notificationPermissionLauncher.launch(
                    android.Manifest.permission.POST_NOTIFICATIONS
                )
            }
        }
    }
```

4. Login logout functionality

   To create different accounts on the same device, use the sdk activation and decoding functionality. To log out of your account, use SdkPushExpress.deactivate(). To log in to another account, create a new setExternalId - SdkPushExpress.setExternalId("new\_ext\_id") run SdkPushExpress.activate(). Example of the implementation of login logout functions in MainActivity.kt:

```kotlin
       private fun logout() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.deactivate()
                Toast.makeText(this@MainActivity, "Logout initiated", Toast.LENGTH_SHORT).show()
                Log.d("Myapp", "Deactivate called - logout initiated")
            } catch (e: Exception) {
                Log.e("Myapp", "Logout failed", e)
            }
        }
    }
    private fun login() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.deactivate()
                val timestamp = System.currentTimeMillis()
                SdkPushExpress.setExternalId("ext_id_$timestamp")
                Log.d("Myapp", "App External ID: ${SdkPushExpress.getExternalId()}")
                SdkPushExpress.activate()
                Toast.makeText(this@MainActivity, "Login initiated", Toast.LENGTH_SHORT).show()
                Log.d("Myapp", "Activate called - login initiated")
            } catch (e: Exception) {
                Log.e("Myapp", "Login failed", e)
            }
        }
    }
```

6. Example processing links from push notifications in MainActivity.kt

```kotlin
override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        Log.d("Myapp", "onNewIntent called with: ${intent.data}")

        setIntent(intent)

        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        if (intent == null) {
            Log.d("Myapp", "handleIntent: intent is null")
            showMainScreen()
            return
        }

        Log.d("Myapp", "handleIntent called, action: ${intent.action}, data: ${intent.data}")

        intent.extras?.let { bundle ->
            Log.d("Myapp", "Intent extras:")
            for (key in bundle.keySet()) {
                Log.d("Myapp", "  $key: ${bundle.get(key)}")
            }
        }

        try {
            when {
                // Deep link from URI
                intent.action == Intent.ACTION_VIEW && intent.data != null -> {
                    val uri = intent.data!!
                    Log.d("Myapp", "Deep Link received: $uri")

                    // Checking the validity of the URI
                    if (isValidUri(uri)) {
                        processDeepLink(uri)
                    } else {
                        Log.w("Myapp", "Invalid URI received: $uri")
                        Toast.makeText(this, "Invalid deep was received link: $uri", Toast.LENGTH_LONG).show()
                        showMainScreen()
                    }
                    return
                }

                // Deep link from extras
                intent.hasExtra("deep_link") -> {
                    val deepLink = intent.getStringExtra("deep_link")
                    Log.d("Myapp", "Deep Link from extra: $deepLink")
                    deepLink?.let {
                        parseAndProcessDeepLink(it)
                    }
                    return
                }

                // Push notification data
                intent.hasExtra("url") || intent.hasExtra("link") -> {
                    val url = intent.getStringExtra("url") ?: intent.getStringExtra("link")
                    Log.d("Myapp", "URL from push: $url")
                    url?.let {
                        parseAndProcessDeepLink(it)
                    }
                    return
                }
            }
        } catch (e: Exception) {
            Log.e("Myapp", "Error handling intent", e)
            Toast.makeText(this, "Link processing error: ${e.message}", Toast.LENGTH_SHORT).show()
        }

        // Normal application launch
        showMainScreen()
    }

    private fun isValidUri(uri: Uri): Boolean {
        val scheme = uri.scheme

        // Check that the URI has a valid scheme
        if (scheme.isNullOrBlank()) {
            return false
        }

        // Check that the scheme is supported
        val supportedSchemes = listOf("http", "https", "myapp", "yourapp")
        return scheme.lowercase() in supportedSchemes
    }

    private fun parseAndProcessDeepLink(urlString: String) {
        try {
            // Check that the string is not empty
            if (urlString.isBlank()) {
                Log.w("Myapp", "Empty URL string")
                Toast.makeText(this, "Empty link received", Toast.LENGTH_SHORT).show()
                return
            }

            // Parse URI
            val uri = Uri.parse(urlString)

            // Check that URI has a valid scheme
            val scheme = uri.scheme
            if (scheme.isNullOrBlank()) {
                Log.w("Myapp", "Invalid URL: no scheme found in '$urlString'")
                Toast.makeText(this, "Invalid link format: scheme is missing (http://, https://, myapp://, etc.)", Toast.LENGTH_LONG).show()
                showMainScreen()
                return
            }

            // Check that the scheme is supported
            val supportedSchemes = listOf("http", "https", "myapp", "yourapp")
            if (scheme.lowercase() !in supportedSchemes) {
                Log.w("Myapp", "Unsupported URL scheme: $scheme in '$urlString'")
                Toast.makeText(this, "Unsupported scheme: $scheme", Toast.LENGTH_SHORT).show()
                showMainScreen()
                return
            }

            // If everything is valid, process the deep link
            processDeepLink(uri)

        } catch (e: Exception) {
            Log.e("Myapp", "Error parsing URL: '$urlString'", e)
            Toast.makeText(this, "Link parsing error: ${e.message}", Toast.LENGTH_LONG).show()
            showMainScreen()
        }
    }

    private fun processDeepLink(uri: Uri) {
        Log.d("Myapp", "Processing deep link: $uri")
        
        val scheme = uri.scheme?.lowercase()
        val host = uri.host?.lowercase()
        
        // Check if it's an external web URL (not our internal domains)
        if ((scheme == "http" || scheme == "https") && 
            host != null && 
            host != "push.express" && 
            host != "yourdomain.com") {
            
            // External URL - redirect to browser
            Log.d("Myapp", "Redirecting external URL to browser: $uri")
            try {
                val browserIntent = Intent(Intent.ACTION_VIEW, uri).apply {
                    // Remove package restriction to allow system to choose browser
                    setPackage(null)
                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                }
                startActivity(browserIntent)
                finish() // Close MainActivity
                return
            } catch (e: Exception) {
                Log.e("Myapp", "Failed to open browser for: $uri", e)
                Toast.makeText(this, "Unable to open link", Toast.LENGTH_SHORT).show()
            }
        }
        
        // Internal deep link - handle within app
        Toast.makeText(this, "Deep link: $uri", Toast.LENGTH_LONG).show()

        // Add your deep link processing logic here
        // For example, navigation to a specific screen
    }

    private fun showMainScreen() {
        Log.d("Myapp", "Show main screen")
    }
```

7. Example MainActivity.kt

```kotlin
   package com.example.kotlinbug3

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import com.example.kotlinbug3.ui.theme.KotlinBug3Theme
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import com.google.firebase.messaging.FirebaseMessaging
import com.pushexpress.sdk.main.SdkPushExpress


const val PUSHEXPRESS_APP_ID = "43766"
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.d("Myapp", "onCreate called")

        askNotificationPermission()

        if (savedInstanceState == null) {
            getFirebaseTokenAndActivate()
        }

        enableEdgeToEdge()
        setContent {
            KotlinBug3Theme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding),
                        onLogoutClick = { logout() },
                        onLoginClick = { login() }
                    )
                }
            }
        }

        handleIntent(intent)
    }

    private fun getFirebaseTokenAndActivate() {
        FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
            if (task.isSuccessful) {
                val token = task.result
                Log.d("MyApp", "Firebase token: $token")

                CoroutineScope(Dispatchers.IO).launch {
                    SdkPushExpress.setFirebaseToken(token)
                    initializeSdk()

                    Log.d("MyApp", "SDK activated with token")
                }
            } else {
                Log.e("MyApp", "Failed to get Firebase token", task.exception)
            }
        }
    }

    private fun initializeSdk() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.initialize(PUSHEXPRESS_APP_ID)
                SdkPushExpress.setExternalId("some_external_id")
                SdkPushExpress.activate()

                Log.d("Myapp", "App Instance Token: ${SdkPushExpress.getInstanceToken()}")
                Log.d("Myapp", "App External ID: ${SdkPushExpress.getExternalId()}")
            } catch (e: Exception) {
                Log.e("Myapp", "SDK initialization failed", e)
            }
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        Log.d("Myapp", "onNewIntent called with: ${intent.data}")

        setIntent(intent)

        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        if (intent == null) {
            Log.d("Myapp", "handleIntent: intent is null")
            showMainScreen()
            return
        }

        Log.d("Myapp", "handleIntent called, action: ${intent.action}, data: ${intent.data}")

        intent.extras?.let { bundle ->
            Log.d("Myapp", "Intent extras:")
            for (key in bundle.keySet()) {
                Log.d("Myapp", "  $key: ${bundle.get(key)}")
            }
        }

        try {
            when {
                // Deep link from URI
                intent.action == Intent.ACTION_VIEW && intent.data != null -> {
                    val uri = intent.data!!
                    Log.d("Myapp", "Deep Link received: $uri")

                    // Checking the validity of the URI
                    if (isValidUri(uri)) {
                        processDeepLink(uri)
                    } else {
                        Log.w("Myapp", "Invalid URI received: $uri")
                        Toast.makeText(this, "Invalid deep was received link: $uri", Toast.LENGTH_LONG).show()
                        showMainScreen()
                    }
                    return
                }

                // Deep link from extras
                intent.hasExtra("deep_link") -> {
                    val deepLink = intent.getStringExtra("deep_link")
                    Log.d("Myapp", "Deep Link from extra: $deepLink")
                    deepLink?.let {
                        parseAndProcessDeepLink(it)
                    }
                    return
                }

                // Push notification data
                intent.hasExtra("url") || intent.hasExtra("link") -> {
                    val url = intent.getStringExtra("url") ?: intent.getStringExtra("link")
                    Log.d("Myapp", "URL from push: $url")
                    url?.let {
                        parseAndProcessDeepLink(it)
                    }
                    return
                }
            }
        } catch (e: Exception) {
            Log.e("Myapp", "Error handling intent", e)
            Toast.makeText(this, "Link processing error: ${e.message}", Toast.LENGTH_SHORT).show()
        }

        // Normal application launch
        showMainScreen()
    }

    private fun isValidUri(uri: Uri): Boolean {
        val scheme = uri.scheme

        // Check that the URI has a valid scheme
        if (scheme.isNullOrBlank()) {
            return false
        }

        // Check that the scheme is supported
        val supportedSchemes = listOf("http", "https", "myapp", "yourapp")
        return scheme.lowercase() in supportedSchemes
    }

    private fun parseAndProcessDeepLink(urlString: String) {
        try {
            // Check that the string is not empty
            if (urlString.isBlank()) {
                Log.w("Myapp", "Empty URL string")
                Toast.makeText(this, "Empty link received", Toast.LENGTH_SHORT).show()
                return
            }

            // Parse URI
            val uri = Uri.parse(urlString)

            // Check that URI has a valid scheme
            val scheme = uri.scheme
            if (scheme.isNullOrBlank()) {
                Log.w("Myapp", "Invalid URL: no scheme found in '$urlString'")
                Toast.makeText(this, "Invalid link format: scheme is missing (http://, https://, myapp://, etc.)", Toast.LENGTH_LONG).show()
                showMainScreen()
                return
            }

            // Check that the scheme is supported
            val supportedSchemes = listOf("http", "https", "myapp", "yourapp")
            if (scheme.lowercase() !in supportedSchemes) {
                Log.w("Myapp", "Unsupported URL scheme: $scheme in '$urlString'")
                Toast.makeText(this, "Unsupported scheme: $scheme", Toast.LENGTH_SHORT).show()
                showMainScreen()
                return
            }

            // If everything is valid, process the deep link
            processDeepLink(uri)

        } catch (e: Exception) {
            Log.e("Myapp", "Error parsing URL: '$urlString'", e)
            Toast.makeText(this, "Link parsing error: ${e.message}", Toast.LENGTH_LONG).show()
            showMainScreen()
        }
    }

    private fun processDeepLink(uri: Uri) {
        Log.d("Myapp", "Processing deep link: $uri")
        
        val scheme = uri.scheme?.lowercase()
        val host = uri.host?.lowercase()
        
        // Check if it's an external web URL (not our internal domains)
        if ((scheme == "http" || scheme == "https") && 
            host != null && 
            host != "push.express" && 
            host != "yourdomain.com") {
            
            // External URL - redirect to browser
            Log.d("Myapp", "Redirecting external URL to browser: $uri")
            try {
                val browserIntent = Intent(Intent.ACTION_VIEW, uri).apply {
                    // Remove package restriction to allow system to choose browser
                    setPackage(null)
                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                }
                startActivity(browserIntent)
                finish() // Close MainActivity
                return
            } catch (e: Exception) {
                Log.e("Myapp", "Failed to open browser for: $uri", e)
                Toast.makeText(this, "Unable to open link", Toast.LENGTH_SHORT).show()
            }
        }
        
        // Internal deep link - handle within app
        Toast.makeText(this, "Deep link: $uri", Toast.LENGTH_LONG).show()

        // Add your deep link processing logic here
        // For example, navigation to a specific screen
    }

    private fun showMainScreen() {
        Log.d("Myapp", "Show main screen")
    }

    private val notificationPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            Toast.makeText(this, "Notifications permission granted", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(
                this,
                "FCM can't post notifications without POST_NOTIFICATIONS permission",
                Toast.LENGTH_LONG
            ).show()
        }
    }

    private fun askNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(
                    this,
                    android.Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                notificationPermissionLauncher.launch(
                    android.Manifest.permission.POST_NOTIFICATIONS
                )
            }
        }
    }

    private fun logout() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.deactivate()
                Toast.makeText(this@MainActivity, "Logout initiated", Toast.LENGTH_SHORT).show()
                Log.d("Myapp", "Deactivate called - logout initiated")
            } catch (e: Exception) {
                Log.e("Myapp", "Logout failed", e)
            }
        }
    }
    private fun login() {
        lifecycleScope.launch {
            try {
                SdkPushExpress.deactivate()
                val timestamp = System.currentTimeMillis()
                SdkPushExpress.setExternalId("ext_id_$timestamp")
                Log.d("Myapp", "App External ID: ${SdkPushExpress.getExternalId()}")
                SdkPushExpress.activate()
                Toast.makeText(this@MainActivity, "Login initiated", Toast.LENGTH_SHORT).show()
                Log.d("Myapp", "Activate called - login initiated")
            } catch (e: Exception) {
                Log.e("Myapp", "Login failed", e)
            }
        }
    }
}

@Composable
fun Greeting(
    name: String,
    modifier: Modifier = Modifier,
    onLogoutClick: () -> Unit = {},
    onLoginClick: () -> Unit = {}
) {
    Column(modifier = modifier.padding(16.dp)) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
        Button(
            onClick = onLogoutClick,
            modifier = Modifier.padding(top = 16.dp)
        ) {
            Text("Logout")
        }
        Button(
            onClick = onLoginClick,
            modifier = Modifier.padding(top = 16.dp)
        ) {
            Text("Login")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    KotlinBug3Theme {
        Greeting("Android")
    }
}
```


# Swift

### PushExpress -- SDK Swift

> [Example Application with Swift SDK](https://github.com/pushexpress/pushexpress-swift-ios-demo)

1. [Create application in Push.Express](#create-application-in-pushexpress)
2. [Add sdk in your application](#add-sdk-in-your-application)
3. [Sandbox or Production mode](#sandbox-or-production-mode)

### Create application in Push.Express

1. Go to your [Push.Express](https://push.express) account
2. Open existing *App* settings or create a new App
3. Switch type application APNs

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-b27ab5c6edd20dd9864552fc7527243a1765dc30%2Fpx-sdk-switch.png?alt=media" alt="" width="800"></div>
4. Enter the Bundle Identifier of your application in the Bundle ID field

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-ec73f17218f346a88820fa4e0167dc331281b3ea%2Fpx-sdk-bundle-id.png?alt=media" alt="" width="800"></div>

> You can view the Bundle Identifier of your application in the XCode TARGETS of your application > General tab in the Identity section, you will see the Bundle Identifier field.

5. Enter your Team ID in the Team ID field.

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-af17266b42233e1647a28d58d1806cb27da62de6%2Fpx-sdk-team-id.png?alt=media" alt="" width="800"></div>

   > You can view your Team ID in the XCode TARGETS of your application > Signing & Capabilities tab in the Signing section, you will see the Team field.
6. Enter your ID APNs Authentication Key in the Key ID field.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-8ceeb2dd6037e9376e84663726f9cab60fa539b4%2Fpx-sdk-key-id.png?alt=media" alt="" width="800"></div>

7\. Enter your Private key APNs Authentication Key from the file .p8 in the Private key field.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-03071fce07604a295a7eed3d06654f05e5ebd1fd%2Fpx-sdk-private-key.png?alt=media" alt="" width="800"></div>

> You can created Private key APNs Authentication Key in your Apple Developer Account > Certificates, Identifiers & Profiles > Keys section

\
\
\
\ <br>

### Add sdk in your application

1. Add the SDK to Xcode using Swift PackageManager. To do this, go to File > Add Packages... > Enter the repository URL SDK (GitHub) <https://github.com/pushexpress/pushexpress-swift-sdk.git> > Click the button Add Package
2. Add Remote Notifications in Background Modes. To do this, go to the XCode TARGETS of your application > Signing & Capabilities > press the button + Capability > select Background Mode from the list > check the box Remote Notifications.
3. Enable Push Notifications in Xcode. Go to the XCode TARGETS of your application > Signing & Capabilities > press the button + Capability > in the list, find and select Push Notifications.
4. Create an application (App ID). Go to [Apple Developer - Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list) > go to the Identifiers section > create a new ID for your app (App ID) > enable the option Push Notifications.
5. Add sdk initialization to AppDelegate.swift

> To configure the sdk, use the following parameters:\
> \
> PUSHEXPRESS\_APP\_ID - The application ID in the Push service.Express, displayed in the section [Applications](https://app.push.express/).\
> myOwnDatabaseExternalId - You can use this parameter to set your own device ID.\
> PushExpressManager.shared.tags - Use tags to set additional attribution. Use the PushExpressManager.shared.tags\["audiences"] tag to transfer the segment code, and Push Express Manager.shared.tags\["webmaster"] to transfer the webmaster code.\
> PushExpressManager.shared.activate и PushExpressManager.shared.deactivate - Activate and deactivate the sdk at the right moment.

```swift
   //
//  AppDelegate.swift
//
//

import Foundation
import UIKit
import SdkPushExpress

class AppDelegate: NSObject, UIApplicationDelegate {
    private let PUSHEXPRESS_APP_ID = "1234-23" // set YOUR OWN ID from Push.Express account page
    private var myOwnDatabaseExternalId = ""      // you can just leave it empty in most cases
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Initialize SDK with PushExpress Application ID
        
        // For minimal initialization (without requesting notification perms
        // and registering for remote notifications) set 'essentialsOnly: true'
        try! PushExpressManager.shared.initialize(appId: PUSHEXPRESS_APP_ID, essentialsOnly: true)
        
        // To call only notification perms request, set 'registerForRemoteNotifications: false'
        // Do not call this if you will request permissions by yourself
        PushExpressManager.shared.requestNotificationsPermission(registerForRemoteNotifications: true)
        
        // DO NOT SET UNLESS YOU ARE 100% SURE WHAT THAT IS!!!
        /*
        myOwnDatabaseExternalId = "my_external_user_id:321"
        PushExpressManager.shared.tags["audiences"] = "my_aud_123"
        PushExpressManager.shared.tags["webmaster"] = "webmaster_name"
        PushExpressManager.shared.tags["ad_id"] = "advertising_id"
        PushExpressManager.shared.tags["my_custom_tag"] = "my_custom_value"
        */
        
        // You can also disable notifications while app is on screen (foreground)
        /*PushExpressManager.shared.foregroundNotifications = false*/
        
        // Activate SDK (starting all network activities)
        try! PushExpressManager.shared.activate(extId: myOwnDatabaseExternalId)
        print("externalId: '\(PushExpressManager.shared.externalId)'")
        
        // If and only if you want to use same app for multiple users
        // call .deactivate() first, than activate() with new extId
        // Better talk with our support, we always ready to help =)
        /*try! PushExpressManager.shared.deactivate()*/
        
        if !PushExpressManager.shared.notificationsPermissionGranted {
            // show your custom message like "Go to iOS Settings and enable notifications"
        }
        
        return true
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
        PushExpressManager.shared.transportToken = tokenParts.joined()
    }
}

```

6. Add the Notification Service Extension to the Xcode project, which will allow you to modify push notifications before displaying them. To do this, File > New > Target > In the window that opens, select Notification Service Extension (under iOS → App Extensions) > Click Next > Specify the extension name (for example, NotificationService) > Click Finish > activate Notification Service Extension. Make sure that the service was added correctly. To do this, TARGETS of your application > General > Frameworks, Libraries, and Embedded Content notificationsvc.appex must be present.
7. Add sdk in TARGETS of your Notification Service Extension > Build Phases > Link Binary With Libraries section. Press " + " and enter the link to the push express sdk in the search box > select SdkPushExpress > click Add
8. Add the sdk code to Notification Service Extension.

```swift
   //
//  NotificationService.swift
//  NotificationService
//
//

import UserNotifications
import SdkPushExpress

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?
    let notificationServiceManager = NotificationManager()

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        if let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent){
           print("bestAttemptContent: \(bestAttemptContent)")
        } else {
           print("Failed to create bestAttemptContent")
        }
        notificationServiceManager.handleNotification(request: request, contentHandler: contentHandler)
    }

    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

}

```

\
\
\
\ <br>

### Sandbox or Production mode

> It is worth considering in which environment your application was launched. Sandbox or Production APNs tokens are not interchangeable.\
> APNs tokens are not generated on Xcode simulators. This is an Apple limitation because simulators do not support registration for remote notifications. Only real devices can request and receive APNs tokens. Make sure that the devices have appeared in the Push Express service in the [devices](https://app.push.express/analytics/devices/) and each device has a Token field filled in.

1. Sandbox (Debug): Used when developing and testing an application via Xcode. The application runs on a device connected to Xcode.\
   If you are using this mode of operation of the application, then set the Sandbox switch in the application settings of your personal account Push Express.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-6cef72c7459ab10d9394f11162a09ca20a67a906%2Fpx-sdk-sandbox.png?alt=media" alt="" width="800"></div>

2. Production (Release): Used when the app is published in the App Store or TestFlight.If you are using this mode of operation of the application, then set the Production switch in the application settings of your personal account Push Express.

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-be29199c7177e2e6ced42750b5531e4ef6f367c9%2Fpx-sdk-production.png?alt=media" alt="" width="800"></div>


# Unity

### PushExpress -- SDK Unity

You will need to integrate your Push.Express account with Firebase.

1. Follow [Firebase Cloud Messaging integration guide](#setup-firebase)
2. [Create application in Push.Express](#setup-pushexpress)
3. [Add sdk in your application](#add-sdk-in-your-application)

### Setup Firebase

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings -> General

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Create new Unity app or just download `google-services.json` from existing app

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-5acc3664b8fdd0d781bb4e5f1441913c34b70c05%2Ffcm-apps-settings.png?alt=media" alt="" width="800"></div>

If you need to create new app, just:

* Register it
* Download `google-services.json`
* Press next-next-next =)
*

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-67fb2209df86c839112438844161af0ea3a4ca2d%2Ffcm-new-android-app.png?alt=media" alt="" width="800"></div>

4. Put `google-services.json` to your Assets dir (like `Assets/google-services.json`)

\
\
\
\ <br>

### Setup Push.Express

#### Get Firebase Private key

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Go to Service accounts, press `Generate new private key` and save it to file `private-key.json` (**you can use same key for all apps**)

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2a00f0365ac888f714f77eef7b0eae1cc1257077%2Ffcm-private-key-page.png?alt=media" alt="" width="800"></div>

#### Integrate your Push.Express App with Firebase

1. Go to your [Push.Express](https://push.express) account
2. Open existing *App* settings or create a new App
3. Switch type application Firebase

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4d4659530a7db5c6d52f24524f829d751c591de6%2Fpx-sdk-switch.png?alt=media" alt="" width="800"></div>
4. Paste `private-key.json` file to *Firebase Admin SDK private key* textbox

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2f39e9dc21f10066a0e59387d28f727cd6345b59%2Fpx-sdk-fcm-key.png?alt=media" alt="" width="800"></div>

\
\
\
\ <br>

### Add sdk in your application

1. Import the main resources TextMeshPro. To do this, go to Window > TextMeshPro > Import TMP Essential Resources > Press All > Press Import.
2. Add package Newton json. To do this, go to Window > Package Manager > In the upper-left corner, click on the + > Install package by name... > Enter in input com.unity.nuget.newtonsoft-json > Press instal.
3. Add SDK Firebase Messaging. To do this, go to the official download page [Firebase Unity SDK](https://firebase.google.com/download/unity) > Download the latest SDK version (as .zip archive) > Unzip the downloaded archive to a convenient location on your computer > Inside the archive you will find several .unitypackage of files for various Firebase modules.\
   Open Unity > Assets > Import Package > Custom Package > Select a file FirebaseMessaging.unitypackage > In the import window, select all files and press Import
4. Add SDK Push Express. To do this, go to the GitHub page [Push Express Unity SDK](https://github.com/pushex/px-unity-sdk) > Download SDK version (as .zip archive) > Unzip the downloaded archive to a convenient location on your computer.\
   Open Unity > Assets > Import Package > Custom Package > Select a file PushExpressSdkV1.3.unitypackage > In the import window, select all files and press Import
5. Add Push Express Prefab на сцену. Open Unity > Open the Project window > Go to Assets > PushExpress > Drag the Prefab from the Project window to the Hierarchy or Scene window > Prefab will appear on the stage and you can adjust its settings.
6. Configure the Push Express Prefab settings. To do this, double-click on it in the Project window > Prefab Mode opens > In the Inspector window, set the parameters of your application. Paste the PUSH EXPRESS\_APP\_ID value of your application into the App Id Prefab field (the value of PUSH EXPRESS\_APP\_ID can be found in the [Applications](https://app.push.express) section of the personal account). Set the Platform and Platform Name parameters according to the type of your application.
7. Add permissions to AndroidManifest if your app is for Android. To do this, go to Assets > Plugins > Android > AndroidManifest.xml > add the code inside the application section

```kotlin
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
```

Add android:theme="@style/Theme.AppCompat.Light.NoActionBar" in the activity tag

```kotlin
   <activity android:name="com.google.firebase.MessagingUnityPlayerActivity" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:theme="@style/Theme.AppCompat.Light.NoActionBar">
```


# Flutter

### PushExpress -- SDK Flutter\*\*

You will need to integrate your Push.Express account with Firebase.

1. Follow [Firebase Cloud Messaging integration guide](#setup-firebase)
2. [Create application in Push.Express](#setup-pushexpress)
3. [Add sdk in your application](#add-sdk-in-your-application)

### Setup Firebase

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings -> General

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Create new Flutter app or just download `google-services.json` from existing app
4. Install and run the FlutterFire CLI

```bash
dart pub global activate flutterfire_cli
```

5. Then, at the root of your Flutter project directory, run this command:

```bash
dart pub global activate flutterfire_cli
```

6. Add your app in firebase project

```bash
flutterfire configure --project=<Firebase Project ID>
```

This automatically registers your per-platform apps with Firebase and adds a lib/firebase\_options.dart configuration file to your Flutter project.\
\
\
\ <br>

### Setup Push.Express

#### Get Firebase Private key

1. Go to [Firebase Console](https://console.firebase.google.com) and create a new project (or use existing one)

   You can use one project for all your apps.
2. Open Project Settings

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a8252e7021a7e46720b40975dcec06834a016718%2Ffcm-project-settings.png?alt=media" alt="" width="800"></div>
3. Go to Service accounts, press `Generate new private key` and save it to file `private-key.json` (**you can use same key for all apps**)

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2a00f0365ac888f714f77eef7b0eae1cc1257077%2Ffcm-private-key-page.png?alt=media" alt="" width="800"></div>

#### Integrate your Push.Express App with Firebase

1. Go to your [Push.Express](https://push.express) account
2. Open existing *App* settings or create a new App
3. Switch type application Firebase

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-4d4659530a7db5c6d52f24524f829d751c591de6%2Fpx-sdk-switch.png?alt=media" alt="" width="800"></div>
4. Paste `private-key.json` file to *Firebase Admin SDK private key* textbox

   <div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-2f39e9dc21f10066a0e59387d28f727cd6345b59%2Fpx-sdk-fcm-key.png?alt=media" alt="" width="800"></div>

\
\
\
\ <br>

### Add sdk in your application

1. Import package in your main.dart file

```dart
   import 'package:push_express_lib/push_express_lib.dart';
```

2. In main.dart add code for init Push Express sdk. Replace the default value of 12345-12 with the PULEXPRESS\_APP\_ID of your application. You can find out your PUSHEXPRESS\_APP\_ID in the [Applications](https:/app.push.express).

```dart
   void initFirebase() async {
    // Initialize firebase app
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
    );

    // Request permissions for push notifications
    await FirebaseMessaging.instance.requestPermission();

    // get unique token for messaging from firebase messaging
    String? token = await FirebaseMessaging.instance.getToken();

    if (token != null) {
        // initialize package
        PushExpressManager().init(
            // your application id from https://app.push.express/
            '12345-12',
            TransportType.fcmData,
            transportToken: token,
            // set property foreground "true" if you need to get notifications when app is in foreground (IOS only)
            foreground: true,
        );
    }
}
```

3. Call initFirebase() in the initState() method in the main.dart file.

```dart
@override
void initState() {
    super.initState();

    // call to init
    initFirebase();
}
```

4. Add a background push notification handler to Firebase Cloud Messaging in the main.dart file.

```dart
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // initialize firebase app
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // call function from the package to handle notifications,
  // and show them properly in background
  NotificationManager().handleNotification(message);
}
```

5. Register the firebaseMessagingBackgroundHandler background push notification handler in the main() function of the main.dart file.

```dart
void main() {
  // ensure widgets are initialized
  WidgetsFlutterBinding.ensureInitialized();

  // handle Firebase background messages
  // and call our handler
  FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

  runApp(const MyApp());
}
```

6. Example of the main.dart file

```dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test_package/firebase_options.dart';
import 'package:push_express_lib/enums/common.dart';
import 'package:push_express_lib/notification_manager.dart';
import 'package:push_express_lib/push_express_lib.dart';

Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  NotificationManager().handleNotification(message);
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    initFirebase();
  }

  void initFirebase() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    await FirebaseMessaging.instance.requestPermission();

    String? token = await FirebaseMessaging.instance.getToken();

    if (token != null) {
      PushExpressManager().init(
        '21486-1212',
        TransportType.fcm,
        transportToken: token,
        foreground: true,
      );
    }

    FirebaseMessaging.onMessage.listen(
      NotificationManager().handleNotification,
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```


# Onesignal

**PushExpress -- SDK Onesignal**

1. Open Onesignal profile.
2. Go to App settings - Keys & IDs
3. Take the OneSignal App ID Rest API Key

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-a6896784607c201eda38aec277525f8531aa9646%2F1.png?alt=media" alt="" width="800"></div>

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-fb88b5067683c2f681e77fc05febf4094c516c2d%2F2.png?alt=media" alt="" width="800"></div>

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-c3972617ab4d061e84663e05f676ee69d539ea7b%2F3.png?alt=media" alt="" width="800"></div>

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-b7686ba910f88cfcdac7883f4f72b7662bef0df7%2F4.png?alt=media" alt="" width="800"></div>

4. Correctly insert them into Push.Express:

<div align="center"><img src="https://4196027459-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEXhSGyb0mYOQ2RZvkDaH%2Fuploads%2Fgit-blob-43a63f7819606b8265d0cf862375bcff74ea6b9c%2Fonesiganladdapp.png?alt=media" alt="" width="800"></div>

Select OneSignal, enter the data, and click Save.


# Applications

Version date: 8 October 2024

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Create an App](#create-an-app)
* [List Apps](#list-apps)
* [Get an App](#get-an-app)
* [Archive an App](#archive-an-app)
* [Delete an App](#delete-an-app)
* [Restore an App](#restore-an-app)
* [Update an App](#update-an-app)
* [Update Credentials And Push Provider](#update-credentials-and-push-provider)
* [Get VAPID key](#get-vapid-key)

***

### Create an App

**POST** `https://core.push.express/api/b/v2/apps`

* **Description**: Creates a new app.

Request \[Firebase]:

```bash
curl --url "https://core.push.express/api/b/v2/apps"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "push_provider": "firebase",
    "name": "appName",
    "link": "https://appurl.com",
    "firebase_creds": {
        "type": "service_account",
        "project_id": "123abc",
        "private_key_id": "123abc",
        "private_key": "-----BEGIN PRIVATE KEY-----\n123abc\n-----END PRIVATE KEY-----\n",
        "client_email": "firebase-viceaccount.com",
        "client_id": "12345",
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
        "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/fake.iam.gserviceaccount.com",
        "universe_domain": "googleapis.com"
	}
}
'
```

Request \[Onesignal]:

```bash
curl --url "https://core.push.express/api/b/v2/apps"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "push_provider": "onesignal",
    "name": "appName",
    "link": "https://appurl.com",
    "onesignal_creds": {
        "app_id": "d65749ed-e9b9-4e4a-a309-010f89bae222",
        "api_key": "appkey1223"
    }
}
'
```

Request \[APNs]:

```bash
curl --url "https://core.push.express/api/b/v2/apps"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "push_provider": "apns",
    "name": "appName",
    "link": "https://appurl.com",
    "apns_creds": {
        "topic": "some.url.fqdn.style",
        "team_id": "123abc",
        "private_key_id": "123abc",
        "private_key": "-----BEGIN PRIVATE KEY-----\n123abc\n-----END PRIVATE KEY-----\n"
  }
}
'
```

Request \[PWA/Web]:

```bash
curl --url "https://core.push.express/api/b/v2/apps"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "push_provider": "webpush",
    "name": "appName PWA",
    "link": "https://appurl.com",
}
'
```

Body params:

* `push_provider`, *required*. Lowercase, must be one of `firebase`, `onesignal`, `apns`, `webpush`.
* `name`, *required*. Application name, any printable characters.
* `push_provider_creds`, *required*. If transport is `firebase` - you need to insert `Firebase Admin SDK IAM key`, same as on the website, it must be a valid JSON. If transport is `onesignal` - you need to provide the key and app ID for OneSignal. If transport is `apns` - provide the APNS credentials including the topic, team ID, authentication key and authentication key ID. If the transport is `webpush`, credentials are not transmitted.
* `link`, *optional*. Link to the Play Market / App Store / etc.

Response:

* 201: New application created

```json
{"id":12345}
```

* `id`, *int*. ID of newly created application.

> If an application has successfully been created then it was automatically added to groups and schedulers which are set to include all apps.

***

### List Apps

**GET** `https://core.push.express/api/b/v2/apps`

* **Description**: Retrieves a list of all apps

> No more then 10000 records can be returned

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps?archived=false"
    --request GET
    --header "Authorization: Bearer ..."
```

Query parameters:

* `archived`, *bool, optional*. Filters archived apps if *true*, otherwise returns active apps.

Response:

* 200: List of applications retrieved

```json
[
"apps": [
    {
        "id":1234,
        "pushexpress_id": "1234-5678",
        "push_provider":"onesignal",
        "name":"appName",
        "link":"https://appurl.com",
        "groups":
        [
            {"id":1,
            "name":"groupName"
            },
            ...
        ],
        "has_firebase_creds":false,
        "has_apns_creds":false,
        "has_onesignal_creds":true, 
        "archived":false
    },
    ...
],
]
```

If user does't own any applications yet then response is an empty list.

* `id`, *int*. ID of application for API.
* `pushexpress_id`, *string*. ID of application for SDK.
* `push_provider`, `name`, `link` see [Create an App](#create-an-app).
* `groups`, *json*. List of all groups that include current app. If an app is not included into any group then `groups` is an empty list.

> PushExpress provides an opportunity to add applications to groups. Full list of groups one owns can be observed [here](https://app.push.express/app/group/) (authentification required).

* `has_firebase_creds`, `has_apns_creds`, `has_onesignal_creds`, *bool*. *True* if all components of corresponding credentials are not empty. *False* if any component missed. List of creds components for each provider type see in [Create an App](#create-an-app).
* `archived`, *bool*. *True* if app archived, *false* otherwise.

**Pagination:**

Now supports one type of pagination:

1. **Page-based pagination** - for UI with numbered pages (limited to 10K records).

**Page-based**

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps?archived=false&page=2&per_page=50"
    --request GET
    --header "Authorization: Bearer ..."
```

Query parameters:

* `archived`, *bool, optional*. Filters archived apps if *true*, otherwise returns active apps.
* `page`, *int, optional*. Page number.
* `per_page`, *int, optional*. The number of entries per page.

````

Response:
```json
{
  "apps": [
		{
			"id": 25330,
			"pushexpress_id": "25330-1269",
			"push_provider": "apns",
			"name": "abcdабвг-3",
			"link": "https://qqw.com",
			"groups": [],
			"has_firebase_creds": false,
			"has_apns_creds": true,
			"has_onesignal_creds": false,
			"archived": false
		},
        ...
	],
  "pagination": {
    "page": 2,
    "per_page": 50,
    "total_pages": 15,
    "total_count": 705
  }
}
````

* `pagination`, *object*. Pagination metadata.
* `pagination.page`, *int*. Current page number.
* `pagination.per_page`, *int*. Records per page.
* `pagination.total_pages`, *int*. Total number of pages.
* `pagination.total_count`, *int*. Total matching records.

**Limit:** Max offset = 10,000 records. UI with numbered pages \[1] \[2] \[3]. Small/medium datasets.

***

### Get an App

**GET** `https://core.push.express/api/b/v2/apps/:app_id`

* **Description**: Retrieves details of a specific app by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id"
    --request GET
    --header "Authorization: Bearer ..."
```

Response:

* 200: Application info retrieved

```json
{
    "id":1234,
    "push_provider":"onesignal",
    "name":"appName",
    "link":"https://appurl.com",
    "groups":
    [
        {"id":1,
        "name":"groupName"
        },
        ...
    ],
    "has_firebase_creds":false,
    "has_apns_creds":false,
    "has_onesignal_creds":true, 
    "archived":false
}
```

Details on response can see in [List Apps](#list-apps)

***

### Archive an App

**POST** `https://core.push.express/api/b/v2/apps/:app_id/archive`

* **Description**: Archives a specific app by its ID. This actions is idempotent, i.e. double archiving is valid.

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/archive"
    --request POST
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

> If an application has successfully been archived then it was automatically removed from all groups and schedulers.

***

### Delete an App

**DELETE** `https://core.push.express/api/b/v2/apps/:app_id`

* **Description**: Deletes a specific app by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/archive"
    --request DELETE
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

> If an application has successfully been deleted then it was automatically removed from all groups and schedulers.

***

### Restore an App

**POST** `https://core.push.express/api/b/v2/apps/:app_id/unarchive`

* **Description**: Restores a specific app by its ID. This actions is idempotent, i.e. double unarchiving is valid.

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/unarchive"
    --request POST
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

> To restore an application does NOT mean to restore all devices it had in the very same moment. Devices will be restored only through a process of syncronization or sdk activity.

***

### Update an App

**PUT** `https://core.push.express/api/b/v2/apps/:app_id`

* **Description**: Updates the base information of a specific app by its ID.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "name": "appName",
    "link": "https://appurl.com"
}
'
```

Response:

* 204: No content

***

### Update Credentials And Push Provider

**PUT** `https://core.push.express/api/b/v2/apps/:app_id/creds/:push_provider`

* **Description**: Updates **both** credentials and push provider for a specific app.

> Supported values for `:push_provider`: `firebase`, `onesignal`, `apns`. WebPush (`webpush`) apps do not have credentials and this endpoint is not applicable to them.

Request \[Firebase]:

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/creds/firebase"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "type": "service_account",
    "project_id": "123abc",
    "private_key_id": "123abc",
    "private_key": "-----BEGIN PRIVATE KEY-----\n123abc\n-----END PRIVATE KEY-----\n",
    "client_email": "firebase-viceaccount.com",
    "client_id": "12345",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/fake.iam.gserviceaccount.com",
    "universe_domain": "googleapis.com"
}
'
```

Request \[Onesignal]:

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/creds/onesignal"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "app_id": "d65749ed-e9b9-4e4a-a309-010f89bae222",
    "api_key": "appkey1223"
}
'
```

Request \[APNs]:

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/creds/apns"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "topic": "some.url.fqdn.style",
    "team_id": "123abc",
    "private_key_id": "123abc",
    "private_key": "-----BEGIN PRIVATE KEY-----\n123abc\n-----END PRIVATE KEY-----\n"
}
'
```

Response:

* 204: No content

***

### Get VAPID key

**GET** `https://core.push.express/api/b/v2/apps/:app_id/vapidkey`

* **Description**: Retrieves vapid key of a specific app by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/apps/:app_id/vapidkey"
    --request GET
    --header "Authorization: Bearer ..."
```

Response:

* 200: Vapid key for app

```json
{"vapid_key_public":"BNIh3BEkoOIt15NJhbcf0XptMQOPFs9WwdRRehtb8k8WbvY9544R5yURPeX3-jN_hQbrg-KYH8-2FGeOfFs9rdQ"} 
```

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/apps/:app_id` returns 404 when provided `app_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Application groups

Version date: 22 April 2025

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Create an Application Group](#create-an-application-group)
* [List Application Groups](#list-application-groups)
* [Get an Application Groups](#get-an-application-groups)
* [Archive an Application Group](#archive-an-application-group)
* [Delete an Application Group](#delete-an-application-group)
* [Restore an Application Group](#restore-an-application-group)
* [Update an Application Group](#update-an-application-group)

***

### Create an Application Group

**POST** `https://core.push.express/api/b/v2/app/groups`

* **Description**: Creates a new application group.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/app/groups"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "app_ids": [25328,25329,25330,25331],
    "name": "appGroupName",
    "all_apps": false,
'
```

Body params:

* `app_ids`, *required*. If all\_apps = flash, you will definitely need to send the application ID.
* `name`, *required*. Application group name, any printable characters.
* `all_apps`, *required*. If true, the group will consist of all active applications. In this case, the app\_ids parameter can be left empty..

Response:

* 200: New application group created

```json
{"id":12345}
```

* `id`, *int*. ID of newly created application group.

***

### List Application Groups

**GET** `https://core.push.express/api/b/v2/app/groups`

* **Description**: Retrieves a list of all application groups

> No more then 10000 records can be returned

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups?archived=false"
    --request GET
    --header "Authorization: Bearer ..."
```

Query parameters:

* `archived`, *bool, optional*. Filters archived application groups if *true*, otherwise returns active application groups.

Response:

* 200: List of application groups retrieved

```json
{
"app_groups": [
    {
        "id": 1443,
        "app_ids": [25328,25329,25330,25331],
        "name": "appGroupName",
        "all_apps": false,
        "archived": false
    },
    ...
],
}
```

* `id`, *int*. ID of application group for API.
* `app_ids`, *string*. ID of applications in group.
* `name`, `all_apps` see [Create an Application Group](#create-an-application-group).
* `archived`, *bool*. *True* if application group archived, *false* otherwise.

  **Pagination:**

Now supports one type of pagination:

1. **Page-based pagination** - for UI with numbered pages (limited to 10K records).

**Page-based**

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups?page=2&per_page=50"
    --request GET
    --header "Authorization: Bearer ..."
```

Query parameters:

* `page`, *int, optional*. Page number.
* `per_page`, *int, optional*. The number of entries per page.

````

Response:
```json
{
 "app_groups": [
    {
        "id": 1443,
        "app_ids": [25328,25329,25330,25331],
        "name": "appGroupName",
        "all_apps": false,
        "archived": false
    },
    ...
  "pagination": {
    "page": 2,
    "per_page": 50,
    "total_pages": 15,
    "total_count": 705
  }
}
````

* `pagination`, *object*. Pagination metadata.
* `pagination.page`, *int*. Current page number.
* `pagination.per_page`, *int*. Records per page.
* `pagination.total_pages`, *int*. Total number of pages.
* `pagination.total_count`, *int*. Total matching records.

**Limit:** Max offset = 10,000 records. UI with numbered pages \[1] \[2] \[3]. Small/medium datasets.

***

### Get an Application Group

**GET** `https://core.push.express/api/b/v2/app/groups/:app_group_id`

* **Description**: Retrieves details of a specific application group by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups/:app_group_id"
    --request GET
    --header "Authorization: Bearer ..."
```

Response:

* 200: Application group info retrieved

```json
{
    "id": 1443,
    "app_ids": [25328,25329,25330,25331],
    "name": "appGroupName",
    "all_apps": false,
    "archived": false
}
```

Details on response can see in [List Application Groups](#list-application-groups)

***

### Archive an Application Group

**POST** `https://core.push.express/api/b/v2/app/groups/:app_group_id/archive`

* **Description**: Archives a specific application group by its ID. This actions is idempotent, i.e. double archiving is valid.

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups/:app_group_id/archive"
    --request POST
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

***

### Delete an Application Group

**DELETE** `https://core.push.express/api/b/v2/app/groups/:app_group_id`

* **Description**: Deletes a specific application group by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups/:app_group_id/archive"
    --request DELETE
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

> If an application group has successfully been deleted then it was automatically removed from all schedulers.

***

### Restore an Application Group

**POST** `https://core.push.express/api/b/v2/apps/:app_group_id/unarchive`

* **Description**: Restores a specific application group by its ID. This actions is idempotent, i.e. double unarchiving is valid.

Request

```bash
curl --url "https://core.push.express/api/b/v2/app/groups/:app_group_id/unarchive"
    --request POST
    --header "Authorization: Bearer ..."
```

Response:

* 204: No content

***

### Update an Application Group

**PUT** `https://core.push.express/api/b/v2/app/groups/:app_group_id`

* **Description**: Updates the base information of a specific app by its ID.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/app/groups/:app_group_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "app_ids": [25329,25330],
    "name": "test",
    "all_apps": false
}
'
```

Response:

* 204: No content

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/app/groups/:app_group_id` returns 404 when provided `app_group_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# App Instances (Devices)

Version date: 19 February 2025

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [List Devices](#list-devices)

***

### List Devices

**POST** `https://core.push.express/api/b/v2/query/app-instances`

* **Description**: Retrieves a list of all devices

Request

```bash
curl --url "https://core.push.express/api/b/v2/query/app-instances"
    --request POST
    --header "Authorization: Bearer ..."
    --data '
{
  "app_ids_include": [
    123,
    234
  ],
  "countries_include": [
    "PT",
    "GB"
  ],
  "languages_include": [],
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "event_include": "install",
  "external_user_id": ["12180927-ab2c-4560-8d59-0d5a31fb9d31","32645c04-3c0b-4baa-b378-2d6ae4397363"],
}
'
```

**Body params:**

* `app_ids_include`, *optional, list of int*. List of application IDs to include into filter for app instances. If it is empty you don't filter by application IDs.
* `countries_include`, *optional, list of int*. List of countries to include into filter for app instances. Follow iso3166\_1\_alpha2 standard. If it is empty you don't filter by countries.
* `languages_include`, *optional, list of int*. List of languages to include into filter for app instances. Follow bcp47\_language\_tag standard. If it is empty you don't filter by languages.
* `audiences_include`, *optional, list of string*. List of audiences to include into filter for app instances. If it is empty you don't filter by audiences.
* `event_include`, *optional, string*. Filters devices which had this event in history. If field is not provided it will be set to `install` i.e. devices are to be chosen regardless of event happened. If it is empty you don't filter by events in history.
* `external_user_id`, *optional, list of string*. List of external use IDs to include into filter for app instances. If it is empty you don't filter by external use IDs.

**Pagination:**

Now supports two types of pagination:

1. **Page-based pagination** - for UI with numbered pages (limited to 10K records).
2. **Cursor-based pagination** - for infinite scroll and large datasets (no limits, better performance).

Both methods work with the same endpoint and are **backward compatible**.

**Page-based**

Request:

```json
{
  "app_ids_include": [
    123,
    234
  ],
  "countries_include": [
    "PT",
    "GB"
  ],
  "languages_include": [],
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "event_include": "install",
  "external_user_id": ["12180927-ab2c-4560-8d59-0d5a31fb9d31","32645c04-3c0b-4baa-b378-2d6ae4397363"],
  "page": 1,
  "per_page": 50
}
```

Response:

```json
{
  "active_count": 1250,
  "app_instances": [...],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total_pages": 15,
    "total_count": 705
  }
}
```

* `active_count`, *int*. Total number of matching devices.
* `app_instances`, *array*. Array of device objects.
* `pagination`, *object*. Pagination metadata.
* `pagination.page`, *int*. Current page number.
* `pagination.per_page`, *int*. Records per page.
* `pagination.total_pages`, *int*. Total number of pages.
* `pagination.total_count`, *int*. Total matching records.

**Limit:** Max offset = 10,000 records. UI with numbered pages \[1] \[2] \[3]. Small/medium datasets.

**Cursor-based**

First request:

```json
{
  "app_ids_include": [
    123,
    234
  ],
  "countries_include": [
    "PT",
    "GB"
  ],
  "languages_include": [],
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "event_include": "install",
  "external_user_id": ["12180927-ab2c-4560-8d59-0d5a31fb9d31","32645c04-3c0b-4baa-b378-2d6ae4397363"],
  "limit": 50
}
```

Response:

```json
{
  "app_instances": [...],
  "next_cursor": "MTcw...",
  "has_more": true
}
```

Next request:

```json
{
"app_ids_include": [
    123,
    234
  ],
  "countries_include": [
    "PT",
    "GB"
  ],
  "languages_include": [],
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "event_include": "install",
  "external_user_id": [],
  "after_cursor": "MTcw...",
  "limit": 50
}
```

* `app_instances`, *array*. Array of device objects.
* `next_cursor`, *string?*. Cursor for next page (null if last page).
* `has_more`, *boolean*. Whether there are more records.

**No limits!** Infinite scroll. Large datasets (>10K records). Data export.

Response:

* 200: List of applications retrieved

```json
{
    "active_count":1,
    "app_instances":[
        {
            "external_id":"",
            "transport_token_masked":"***",
            "language":"en",
            "country":"USA",
            "last_active":"2022-04-27 06:11:46",
            "time_install":"2022-04-27T06:07:41Z",
            "notif_perm_granted":true,
            "platform_name":"12",
            "events":{
                "reg":true,
                "dep":true
            },
            "tags":{
                "ad_id":"",
                "webmaster":"",
                "audiences":"123"
            }
        }
    ]
}
```

If user does't own any devices yet then response is an empty list with active\_count equals 0

* `active_count`, *int*. The number of active device instances associated with the user based on the request data.
* `app_instances`, *array*. List of devices matching the specified filters, each represented as an object with the following attributes:
  * `external_id`, *string*. Unique identifier assigned to the device.
  * `transport_token_masked`, *string*. Masked transport token used for secure communication.
  * `language`, *string*. Language setting of the device, represented as an ISO 639-1 code (e.g., "en").
  * `country`, *string*. Country code of the device, represented as an ISO 3166-1 alpha-3 code (e.g., "USA").
  * `last_active`, *datetime*. The last recorded activity timestamp of the device in ISO 8601 format.
  * `time_install`, *datetime*. Timestamp indicating when the application was installed on the device, in ISO 8601 format.
  * `notif_perm_granted`, *boolean*. – Indicates whether the user has granted notification permissions (true if granted, false otherwise).
  * `platform_name`, *string*. Operating system version of the device.
  * `events`, *json*. Model that contains information about reg and dep events status.
  * `reg`,*boolean*. Flag for reg event status (true if it is reg event, false otherwise).
  * `dep`, *boolean*. Flag for dep event status (true if it is dep event, false otherwise).
  * `events`, *json*. Model that contains information about reg and dep events status.
  * `tags`, *optional*. User-specified string tags, map of strings with string keys. Predefined tag names: `ad_id`, `offer`, `webmaster`

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 404 is returned when resource doesn't exist. Check your request data.
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Pushes

Version date: 27 January 2026

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [PushExpress -- pushes API reference](#pushexpress----pushes-api-reference)
  * [Endpoints](#endpoints)
  * [Create a Push](#create-a-push)
  * [Update a Push](#update-a-push)
  * [Delete a Push](#delete-a-push)
  * [Activate a Push](#activate-a-push)
  * [Deactivate a Push](#deactivate-a-push)
  * [Bind Pushes to a Scheduler](#bind-pushes-to-a-scheduler)
  * [Get a Push](#get-a-push)
  * [List Pushes by Scheduler](#list-pushes-by-scheduler)
  * [Error handling](#error-handling)

***

## Create a Push

**POST** `https://core.push.express/api/b/v2/pushes`

* **Description**: Creates a new push. Each push must have at least 1 translation. In the moment of sending PushExpress choses which translation is to be sent according to device language. If push does not have translation for current devices then default language translation is sent.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "default_lang": "en",
  "translations": [
    {
      "language": "en",
      "title": "Title Of My Push",
      "text": "Text of my push",
      "image": "link-to/image/for.my.push",
      "icon": "link-to/icon/for.my.push",
      "link": "link-for.my.push"
    },
    {
      "language": "pt",
      "title": "Título do meu push",
      "text": "Texto do meu push"
    },
    ...
  ]
}
```

Body params:

* `default_lang`, *required*. Must be 2-digit language code. Follow bcp47 standard. This language will be chosen for devices with languages not mentioned in
* `translations`, *required*. List of translations for this push. Must include translation for language set by `default_lang` parameter.
* `language`, *required*. Must be 2-digit language code. Follow bcp47 standard.
* `title`, *required*. This is a short text in the head of notification.
* `text`, *required*. This is a longer text under the title.
* `image`, *optional*. An image to be shown in push body.
* `icon`, *optional*. An icon to be shown in push preview.
* `link`, *optional*. Link to be followed when user clicks on push.

Response:

* 201: New push with translations created

```json
{
  "id": 12345,
  "translations": [
    {
      "id": 111,
      "language": "en"
    },
    {
      "id": 222,
      "language": "pt"
    },
    ...
  ]
}
```

* `id`, *int*. ID of newly created push.
* `translations`, *object*. List of translations created.

> If you want to send some push via scheduler you should first create push and then bind it to some scheduler. Newly created push is not attached to any scheduler.

***

## Update a Push

**PUT** `https://core.push.express/api/b/v2/pushes/:push_id`

* **Description**: Updates an existing push notification. All translations will be replaced with new ones provided in the request. The push must belong to the authenticated user.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes/:push_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "default_lang": "en",
  "translations": [
    {
      "language": "en",
      "title": "Updated Title",
      "text": "Updated text content",
      "image": "link-to/updated-image.jpg",
      "icon": "link-to/updated-icon.jpg",
      "link": "updated-link.com"
    },
    {
      "language": "de",
      "title": "Aktualisierter Titel",
      "text": "Aktualisierter Textinhalt"
    }
  ]
}
```

URL params:

* `push_id`, *int*. ID of the push to update.

Body params:

* `default_lang`, *required*. Must be 2-digit language code. Follow bcp47 standard.
* `translations`, *required*. List of translations for this push. Must include translation for language set by `default_lang` parameter.
* `language`, *required*. Must be 2-digit language code. Follow bcp47 standard.
* `title`, *required*. This is a short text in the head of notification.
* `text`, *required*. This is a longer text under the title.
* `image`, *optional*. An image to be shown in push body.
* `icon`, *optional*. An icon to be shown in push preview.
* `link`, *optional*. Link to be followed when user clicks on push.

Response:

* 204: Push successfully updated, no content returned
* 404: Push not found or doesn't belong to user
* 400: Validation error (e.g., duplicate language codes, missing default language translation)

***

## Delete a Push

**DELETE** `https://core.push.express/api/b/v2/pushes/:push_id`

* **Description**: Deletes a push notification and all its translations. The push must belong to the authenticated user. This action cannot be undone.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes/:push_id"
    --request DELETE
    --header "Authorization: Bearer ..."
```

URL params:

* `push_id`, *int*. ID of the push to delete.

Response:

* 204: Push successfully deleted, no content returned
* 404: Push not found or doesn't belong to user

***

## Activate a Push

**POST** `https://core.push.express/api/b/v2/pushes/:push_id/activate`

* **Description**: Activates a push notification, making it eligible for sending. The push must belong to the authenticated user.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes/:push_id/activate"
    --request POST
    --header "Authorization: Bearer ..."
```

URL params:

* `push_id`, *int*. ID of the push to activate.

Response:

* 204: Push successfully activated, no content returned
* 404: Push not found or doesn't belong to user

***

## Deactivate a Push

**POST** `https://core.push.express/api/b/v2/pushes/:push_id/deactivate`

* **Description**: Deactivates a push notification, preventing it from being sent. The push must belong to the authenticated user. Deactivated pushes can be reactivated later.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes/:push_id/deactivate"
    --request POST
    --header "Authorization: Bearer ..."
```

URL params:

* `push_id`, *int*. ID of the push to deactivate.

Response:

* 204: Push successfully deactivated, no content returned
* 404: Push not found or doesn't belong to user

***

## Bind Pushes to a Scheduler

**PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes`

* **Description**: Binds a list of pushes \[]P to scheduler S with unbinding all pushes previously bound to S. If S already had any pushes attached they all will be unattached. You can only bind pushes P that are not attached to any other scheduler.

Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "push_ids": [123, 234, ...]
}
```

URL params:

* `sched_type`, *string*. Type of scheduler pushes are to be bound to. Accepted values: `onesend` (one-time sending), `list` (smart weekly), `conversion` (auto-push). Note: smart weekly schedulers use `list` as sched\_type, not `smartweekly`.
* `sched_id`, *int*. This is an ID of scheduler pushes are to be bound to.

Body params:

* `push_ids`, *required*. A list of push IDs to be bound to scheduler. Requirements for each push in the list:

  * must belong to the authenticated user
  * must not be attached to any other scheduler
  * must not be deactivated (status = 0 / archived)

  If an empty list is provided then all pushes previously bound to scheduler become unbound and no new pushes are attached to scheduler. Null value is prohibited.

Response:

* 204: No content

***

## Get a Push

**GET** `https://core.push.express/api/b/v2/pushes/:push_id`

* **Description**: Retrieves details of a specific push notifications by its ID.

Request

```bash
curl --url "https://core.push.express/api/b/v2/pushes/:push_id"
    --request GET
    --header "Authorization: Bearer ..."
```

Response:

* 201: New push with translations created

```json
{
  "id": 484772,
  "shed_id": 0,
  "archived": false,
  "default_lang": "en",
  "translations": [
    {
      "id": 4019723,
      "language": "en",
      "title": "Title Of My Push",
      "text": "Text of my push",
      "image": "",
      "icon": "",
      "link": ""
    },
    {
      "id": 4019724,
      "language": "pt",
      "title": "Título do meu push",
      "text": "Texto do meu push",
      "image": "",
      "icon": "",
      "link": ""
    }
  ]
}
```

***

## List Pushes by Scheduler

**GET** `https://core.push.express/api/b/v2/pushlist/:sched_id/:sched_type`

* **Description**: Returns a list of pushes attached to a specific scheduler. Supports both page-based and cursor-based pagination.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/pushlist/14194/onesend?page=1&per_page=20"
    --request GET
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler.
* `sched_type`, *required, string*. Type of scheduler. One of: `onesend`, `list` (smart weekly), `conversion`.

### Query params:

* `archived`, *optional, bool*. Filter by archived status of pushes.

**Page-based pagination** (default):

* `page`, *optional, int*. Page number, default `1`.
* `per_page`, *optional, int*. Items per page, default `20`.

**Cursor-based pagination** (used when `after_cursor` or `limit` is provided without `page`/`per_page`):

* `after_cursor`, *optional, string*. Opaque cursor value from the previous response `next_cursor` field.
* `limit`, *optional, int*. Number of items to return per page.

### Response:

**Page-based** — 200:

```json
{
  "pushes": [
    {
      "id": 484772,
      "shed_id": 14194,
      "archived": false,
      "default_lang": "en",
      "translations": [
        {
          "id": 4019723,
          "language": "en",
          "title": "Title Of My Push",
          "text": "Text of my push",
          "image": "",
          "icon": "",
          "link": ""
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total_pages": 1,
    "total_count": 3
  }
}
```

**Cursor-based** — 200:

```json
{
  "pushes": [
    {
      "id": 484772,
      "shed_id": 14194,
      "archived": false,
      "default_lang": "en",
      "translations": [...]
    }
  ],
  "next_cursor": "eyJpZCI6NDg0NzcyfQ==",
  "has_more": true
}
```

* `next_cursor`, *string or null*. Cursor to use as `after_cursor` in the next request. `null` if there are no more items.
* `has_more`, *bool*. Indicates whether more items exist after the current page.

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 403 is returned when access to resource for current user is denied.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes` returns 404 when provided `sched_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Instant sending

Version date: 20 December 2024

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Send Push Instantly](#send-push-instantly)

***

## Send Push Instantly

**POST** `https://core.push.express/api/b/v2/send/instant`

* **Description**: This endpoint allows user to send push right now to devices chosen by filters. It creates new one-time sending with due moment 2 minutes after request, creates and attaches new push with translations to this newly created one-time sending in future.

Request

```bash
curl --url "https://core.push.express/api/b/v2/send/instant"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Name of scheduler",
  "filters": {
    "all_apps": false,
    "app_ids_include": [
      123,
      234
    ],
    "app_ids_exclude": [
      345
    ],
    "app_groups_include": [
      1
    ],
    "app_groups_exclude": null,
    "all_countries": false,
    "countries_include": [
      "PT",
      "GB"
    ],
    "countries_exclude": [],
    "all_languages": true,
    "languages_include": [],
    "languages_exclude": [
      "pt"
    ],
    "all_audiences": false,
    "audiences_include": [
      "myaud1_1234",
      "myaud2_1234"
    ],
    "audiences_exclude": [
      "myaud3_1234"
    ],
    "audience_groups_include": [
      11,
      22
    ],
    "audience_groups_exclude": [
      33
    ],
    "event_include": ["install","reg"],
    "event_exclude": ["dep"],
    "external_ids":[
        "0b50bec7-ea47-437c-be5b-c4344bf0fc53",
        "b8c966b3-83be-44d5-a0f1-67faf45278e5"
    ]
  },
  "push": {
    "default_lang": "en",
    "translations": [
      {
        "language": "en",
        "title": "Title Of My Push",
        "text": "Text of my push",
        "image": "link-to/image/for.my.push",
        "icon": "link-to/icon/for.my.push",
        "link": "link-for.my.push"
      },
      {
        "language": "pt",
        "title": "Título do meu push",
        "text": "Texto do meu push"
      },
      ...
    ]
  }
}
```

Body params:

* `name`, *required, string*. Name of scheduler.
* `filters`, *required, object*. Filters to choose devices. More on filters see in `one-time sending in future API reference`.
* `push`, *required, object*. Push with translations to send. More on push parameters see in `pushes API reference`.

Response:

* 201: New one-time sending in future created

```json
{
  "sched_id": 12345,
  "push_id": 9876,
  "translations": [
    {
      "id": 111,
      "language": "en"
    },
    {
      "id": 222,
      "language": "pt"
    },
    ...
  ]
}
```

* `sched_id`, *int*. ID of newly created one-time sending.
* `push_id`, *int*. ID of newly created push.
* `translations`, *object*. Info about newly created translations for push.

> If 201 got there is no way to cancel sending via API. Use web personal account to do it if needed.

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 403 is returned when access to resource for current user is denied.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes` returns 404 when provided `sched_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# One-time sending in future

```
# PushExpress -- one-time sending in future API reference
```

Version date: 20 December 2024

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

### Endpoints

* [Create a OneSend Scheduler](#create-a-onesend-scheduler)
* [Get a OneSend Scheduler](#get-a-onesend-scheduler)
* [List OneSend Schedulers](#list-onesend-schedulers)
* [Update a OneSend Scheduler](#update-a-onesend-scheduler)
* [Delete a OneSend Scheduler](#delete-a-onesend-scheduler)
* [Activate a OneSend Scheduler](#activate-a-onesend-scheduler)
* [Deactivate a OneSend Scheduler](#deactivate-a-onesend-scheduler)

***

### Create a OneSend Scheduler

**POST** `https://core.push.express/api/b/v2/sched/onesend`

* **Description**: Creates a new one-time sending in future. Scheduler allows tuning the group of receivers via filters and choosing a moment for sending in future. No sending happens if scheduler has no attached pushes.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Name of scheduler",

  "date": "2006-01-02",
  "time": "15:04",
  "local_time": false,
  "timezone": "-07:00",

  "all_apps": false,
  "app_ids_include": [
    123,
    234
  ],
  "app_ids_exclude": [
    345
  ],
  "app_groups_include": [
    1
  ],
  "app_groups_exclude": null,

  "all_countries": false,
  "countries_include": [
    "PT",
    "GB"
  ],
  "countries_exclude": [],

  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [
    "pt"
  ],

  "all_audiences": false,
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "audiences_exclude": [
    "myaud3_1234"
  ],
  "audience_groups_include": [
    11,
    22
  ],
  "audience_groups_exclude": [
    33
  ],

  "event_include": ["install","reg"],
  "event_exclude": ["dep"],
  "external_ids": [
        "0b50bec7-ea47-437c-be5b-c4344bf0fc53",
        "b8c966b3-83be-44d5-a0f1-67faf45278e5"
]
}
```

#### Body params:

part 1, name

* `name`, *required, string*. Name of scheduler.

part 2, moment of sending

* `date`, *required, string*. Format YYYY-MM-DD
* `time`, *required, string*. Format HH-MM
* `local_time`, *required, bool*. "True" stands for "send by local time of devices", "false" stands for "send by one chosen timezone".
* `timezone`, *required if local\_time=false, excluded if local\_time=true, string*. Format +HH:MM. Offset of UTC.

part 3, filters, applications

* `all_apps`, *optional, bool*. Flag to include all applications into sending. If chosen True then `app_ids_include` and `app_groups_include` must be be omitted.
* `app_ids_include`, *optional, list of int*. List of application IDs to include into sending.
* `app_ids_exclude`, *optional, list of int*. List of application IDs to exclude from sending.
* `app_groups_include`, *optional, list of int*. List of application group IDs to include into sending. Make your application groups and manage them [here](https://app.push.express/app/group/)
* `app_groups_exclude`, *optional, list of int*. List of application group IDs to exclude from sending.

> If filters result in an empty set of applications then no scheduler is created. Corresponding `_include` and `_exclude` lists must have no common elements.

part 4, filters, countries

* `all_countries`, *optional, bool*. Flag to include devices with any country into sending. If chosen True then `countries_include` must be be omitted.
* `countries_include`, *optional, list of int*. List of countries to include into sending. Follow iso3166\_1\_alpha2 standard.
* `countries_exclude`, *optional, list of int*. List of countries to exclude from sending. Should be provided only if `all_countries` = true. Follow iso3166\_1\_alpha2 standard.

part 5, filters, languages

* `all_languages`, *optional, bool*. Flag to include devices with any language into sending. If chosen True then `languages_include` must be be omitted.
* `languages_include`, *optional, list of int*. List of languages to include into sending. Follow bcp47\_language\_tag standard.
* `languages_exclude`, *optional, list of int*. List of languages to exclude from sending. Should be provided only if `all_languages` = true. Follow bcp47\_language\_tag standard.

part 6, filters, audiences

In PushExpress audience is a tag that can be attached to devices. Some devices may have no audience.

> Create audiences or segments [here](https://app.push.express/postbacks/offers/), create audience or segment groups [here](https://app.push.express/postbacks/group/)

* `all_audiences`, *optional, bool*. Flag to include all audiences into sending.

If flag is on then only devices with any audience defined will be reached out while devices with no audience will not be reached out.

If one wants to reach out to all devices regardless of audience they should set `all_audiences` to false and omit `audiences_include` with `audience_groups_include`.

If chosen True then `audiences_include` and `audience_groups_include` must be be omitted.

* `audiences_include`, *optional, list of string*. List of audiences to include into sending.
* `audiences_exclude`, *optional, list of int*. List of audiences to exclude from sending.
* `audience_groups_include`, *optional, list of int*. List of audience group IDs to include into sending.
* `audience_groups_exclude`, *optional, list of int*. List of audience group IDs to exclude from sending.

> Corresponding `_include` and `_exclude` lists must have no common elements.

part 7, filters, events

Here you can specify both system events: install, reg, dep, and custom ones. The installation includes all devices, reg - only registered ones, dep - those who have replenished the balance at least once. Custom events can be created [here](https://docs.push.express/api/custom-events). More information about the events is [here](https://docs.push.express/quckstart/custom_events).

* `event_include`, *optional, list of string*. Filters devices which had this event in history. If field is not provided it will be set to `install` i.e. devices are to be chosen regardless of event happened.
* `event_exclude`, *optional, list of string*. Filters devices which did not have this event in history. If field not provided devices are to be chosen regardless of event that did not happen.

part 8, filters, external id

You can only send to certain devices by transmitting their Extrenal IDs.

> You can view the device ID in the devices section [here](https://app.push.express/analytics/devices/) in the ExternalUserId field. Make sure that you have configured the creation of an ExternalUserId when implementing the sdk.

* `external_ids`, *optional, list of string*. List of external IDs devices.

#### Response:

* 201: New one-time sending in future created

```json
{"id":12345}
```

* `id`, *int*. ID of newly created one-time sending.

> No sending happens if scheduler has no attached pushes.

#### Next step

To create push, refer to the [Push Creation API](/api/pushes#create-a-push).

To attach push to the scheduler, refer to the [Push Attachment API](/api/pushes#bind-pushes-to-a-scheduler).

***

### Get a OneSend Scheduler

**GET** `https://core.push.express/api/b/v2/sched/onesend/:sched_id`

* **Description**: Retrieves details of a specific scheduler by its ID.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend/:sched_id"
    --request GET
    --header "Authorization: Bearer ..."
```

***

#### Response

* 200

```json
{
  "id": 14194,
  "name": "One Sned API",
  "date": "2025-05-28",
  "time": "13:02",
  "local_time": false,
  "timezone": "+00:00",
  "app_ids_include": [
    33337,
    35593,
    35897,
    37613,
    37809,
    39026
  ],
  "app_ids_exclude": null,
  "app_groups_include": [],
  "app_groups_exclude": [],
  "all_apps": false,
  "all_countries": false,
  "all_languages": false,
  "all_audiences": true,
  "countries_include": null,
  "countries_exclude": null,
  "languages_include": null,
  "languages_exclude": null,
  "event_include": "install",
  "event_exclude": "",
  "audiences_include": null,
  "audiences_exclude": null,
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "external_ids": null,
  "archived": false
}
```

***

### List OneSend Schedulers

**GET** `https://core.push.express/api/b/v2/sched/onesend`

* **Description**: Returns a paginated list of one-time sending schedulers for the current user.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend?page=1&per_page=20"
    --request GET
    --header "Authorization: Bearer ..."
```

#### Query params:

* `archived`, *optional, bool*. Filter by archived status. `true` — return only archived schedulers, `false` (default) — return only active.
* `page`, *optional, int*. Page number, default `1`.
* `per_page`, *optional, int*. Items per page, default `20`.
* `name`, *optional, string*. Filter by scheduler name (partial match).
* `app_id`, *optional, string*. Filter schedulers that include a specific application ID.
* `country`, *optional, string*. Filter by country code (iso3166\_1\_alpha2).
* `language`, *optional, string*. Filter by language code (bcp47).
* `sort_by`, *optional, string*. Field to sort by.
* `sort_dir`, *optional, string*. Sort direction: `asc` or `desc`.

#### Response:

* 200

```json
{
  "schedulers": [
    {
      "id": 14194,
      "name": "One Send API",
      "date": "2025-05-28",
      "time": "13:02",
      "local_time": false,
      "timezone": "+00:00",
      "status": false,
      "app_ids_include": [33337, 35593],
      "app_ids_exclude": null,
      "app_groups_include": [],
      "app_groups_exclude": [],
      "all_apps": false,
      "all_countries": false,
      "all_languages": false,
      "all_audiences": true,
      "countries_include": null,
      "countries_exclude": null,
      "languages_include": null,
      "languages_exclude": null,
      "event_include": ["install"],
      "event_exclude": [],
      "audiences_include": null,
      "audiences_exclude": null,
      "audience_groups_include": [],
      "audience_groups_exclude": [],
      "external_ids": null
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total_pages": 3,
    "total_count": 47
  }
}
```

***

### Update a OneSend Scheduler

**PUT** `https://core.push.express/api/b/v2/sched/onesend/:sched_id`

* **Description**: Fully replaces an existing one-time sending scheduler with new data. The request body has the same structure as [Create a OneSend Scheduler](#create-a-onesend-scheduler).

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend/:sched_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Updated scheduler name",
  "date": "2025-12-31",
  "time": "10:00",
  "local_time": false,
  "timezone": "+03:00",
  "all_apps": false,
  "app_ids_include": [123, 234],
  "app_ids_exclude": [],
  "app_groups_include": [],
  "app_groups_exclude": [],
  "all_countries": true,
  "countries_include": [],
  "countries_exclude": [],
  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [],
  "all_audiences": false,
  "audiences_include": [],
  "audiences_exclude": [],
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "event_include": ["install"],
  "event_exclude": []
}'
```

#### URL params:

* `sched_id`, *required, int*. ID of the scheduler to update.

#### Body params:

Same as [Create a OneSend Scheduler](#create-a-onesend-scheduler).

#### Response:

* 200: Scheduler successfully updated

```json
{"id": 14194}
```

* 404: Scheduler not found or doesn't belong to user

***

### Delete a OneSend Scheduler

**DELETE** `https://core.push.express/api/b/v2/sched/onesend/:sched_id`

* **Description**: Deletes a one-time sending scheduler along with all its attached pushes and their translations. This action cannot be undone.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend/:sched_id"
    --request DELETE
    --header "Authorization: Bearer ..."
```

#### URL params:

* `sched_id`, *required, int*. ID of the scheduler to delete.

#### Response:

* 204: Scheduler and all its pushes successfully deleted, no content returned
* 404: Scheduler not found or doesn't belong to user

***

### Activate a OneSend Scheduler

**POST** `https://core.push.express/api/b/v2/sched/onesend/:sched_id/activate`

* **Description**: Activates a one-time sending scheduler, allowing it to execute at the scheduled time.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend/:sched_id/activate"
    --request POST
    --header "Authorization: Bearer ..."
```

#### URL params:

* `sched_id`, *required, int*. ID of the scheduler to activate.

#### Response:

* 204: Scheduler successfully activated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

### Deactivate a OneSend Scheduler

**POST** `https://core.push.express/api/b/v2/sched/onesend/:sched_id/deactivate`

* **Description**: Deactivates a one-time sending scheduler, preventing it from executing. Can be reactivated later.

#### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/onesend/:sched_id/deactivate"
    --request POST
    --header "Authorization: Bearer ..."
```

#### URL params:

* `sched_id`, *required, int*. ID of the scheduler to deactivate.

#### Response:

* 204: Scheduler successfully deactivated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

### Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 403 is returned when access to resource for current user is denied.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes` returns 404 when provided `sched_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Smart weekly sending

Version date: 24 February 2025

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Create a Smart weekly Scheduler](#create-a-smart-weekly-scheduler)
* [Get a Smart weekly Scheduler](#get-a-smart-weekly-scheduler)
* [List Smart weekly Schedulers](#list-smart-weekly-schedulers)
* [Update a Smart weekly Scheduler](#update-a-smart-weekly-scheduler)
* [Delete a Smart weekly Scheduler](#delete-a-smart-weekly-scheduler)
* [Activate a Smart weekly Scheduler](#activate-a-smart-weekly-scheduler)
* [Deactivate a Smart weekly Scheduler](#deactivate-a-smart-weekly-scheduler)

***

## Create a Smart weekly Scheduler

**POST** `https://core.push.express/api/b/v2/sched/smartweekly`

* **Description**: Creates a new smart weekly sending. Scheduler allows tuning the group of receivers via filters and choosing date and time for weekly sending. No sending happens if scheduler has no attached pushes.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Name of scheduler",

  "time_send": [
    {
      "day": 3,
      "times": ["07:00", "18:10"]
    },
    {
      "day": 0,
      "times": ["12:15", "13:45"]
    }
  ],
  "local_time": false,
  "timezone": "-07:00",

  "all_apps": false,
  "app_ids_include": [
    123,
    234
  ],
  "app_ids_exclude": [
    345
  ],
  "app_groups_include": [
    1
  ],
  "app_groups_exclude": null,

  "all_countries": false,
  "countries_include": [
    "PT",
    "GB"
  ],
  "countries_exclude": [],

  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [
    "pt"
  ],

  "all_audiences": false,
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "audiences_exclude": [
    "myaud3_1234"
  ],
  "audience_groups_include": [
    11,
    22
  ],
  "audience_groups_exclude": [
    33
  ],

  "event_include": ["install","reg"],
  "event_exclude": ["dep"]
}
```

### Body params

part 1, name

* `name`, *required, string*. Name of scheduler.

part 2, moment of sending

* `time_send` *required, list of objects* – A list of days of the week and sending times.
  * `day` *required, int* – Represents the day of the week when the sending should occur.
  * `times` *required, list of string* – A list of sending times (format HH:MM).
* `local_time`, *required, bool*. "True" stands for "send by local time of devices", "false" stands for "send by one chosen timezone".

part 3, filters, applications

* `all_apps`, *optional, bool*. Flag to include all applications into sending. If chosen True then `app_ids_include` and `app_groups_include` must be be omitted.
* `app_ids_include`, *optional, list of int*. List of application IDs to include into sending.
* `app_ids_exclude`, *optional, list of int*. List of application IDs to exclude from sending.
* `app_groups_include`, *optional, list of int*. List of application group IDs to include into sending. Make your application groups and manage them [here](https://app.push.express/app/group/)
* `app_groups_exclude`, *optional, list of int*. List of application group IDs to exclude from sending.

> If filters result in an empty set of applications then no scheduler is created. Corresponding `_include` and `_exclude` lists must have no common elements.

part 4, filters, countries

* `all_countries`, *optional, bool*. Flag to include devices with any country into sending. If chosen True then `countries_include` must be be omitted.
* `countries_include`, *optional, list of int*. List of countries to include into sending. Follow iso3166\_1\_alpha2 standard.
* `countries_exclude`, *optional, list of int*. List of countries to exclude from sending. Should be provided only if `all_countries` = true. Follow iso3166\_1\_alpha2 standard.

part 5, filters, languages

* `all_languages`, *optional, bool*. Flag to include devices with any language into sending. If chosen True then `languages_include` must be be omitted.
* `languages_include`, *optional, list of int*. List of languages to include into sending. Follow bcp47\_language\_tag standard.
* `languages_exclude`, *optional, list of int*. List of languages to exclude from sending. Should be provided only if `all_languages` = true. Follow bcp47\_language\_tag standard.

part 6, filters, audiences

In PushExpress audience is a tag that can be attached to devices. Some devices may have no audience.

> Create audiences or segments [here](https://app.push.express/postbacks/offers/), create audience or segment groups [here](https://app.push.express/postbacks/group/)

* `all_audiences`, *optional, bool*. Flag to include all audiences into sending.

If flag is on then only devices with any audience defined will be reached out while devices with no audience will not be reached out.

If one wants to reach out to all devices regardless of audience they should set `all_audiences` to false and omit `audiences_include` with `audience_groups_include`.

If chosen True then `audiences_include` and `audience_groups_include` must be be omitted.

* `audiences_include`, *optional, list of string*. List of audiences to include into sending.
* `audiences_exclude`, *optional, list of int*. List of audiences to exclude from sending.
* `audience_groups_include`, *optional, list of int*. List of audience group IDs to include into sending.
* `audience_groups_exclude`, *optional, list of int*. List of audience group IDs to exclude from sending.

> Corresponding `_include` and `_exclude` lists must have no common elements.

part 7, filters, events

Here you can specify both system events: install, reg, dep, and custom ones. The installation includes all devices, reg - only registered ones, dep - those who have replenished the balance at least once. Custom events can be created [here](https://docs.push.express/api/custom-events). More information about the events is [here](https://docs.push.express/quckstart/custom_events).

* `event_include`, *optional, list of string*. Filters devices which had this event in history. If field is not provided it will be set to `install` i.e. devices are to be chosen regardless of event happened.
* `event_exclude`, *optional, list of string*. Filters devices which did not have this event in history. If field not provided devices are to be chosen regardless of event that did not happen.

### Response

* 201

```json
{"id":12345}
```

* `id`, *int*. ID of newly created smart weekly sending.

> No sending happens if scheduler has no attached pushes.

### Next step

To create push, refer to the [Push Creation API](/api/pushes#create-a-push).

To attach push to the scheduler, refer to the [Push Attachment API](/api/pushes#bind-pushes-to-a-scheduler).

***

## Get a Smart weekly Scheduler

**GET** `https://core.push.express/api/b/v2/sched/smartweekly/:sched_id`

* **Description**: Retrieves details of a specific scheduler by its ID.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly/:sched_id"
    --request GET
    --header "Authorization: Bearer ..."
```

***

### Response

* 200

```json
{
  "id": 18998,
  "name": "Test Web push",
  "time_send": [
    {
      "day": 3,
      "times": [
        "15:06"
      ]
    },
    {
      "day": 2,
      "times": [
        "14:24"
      ]
    },
    {
      "day": 4,
      "times": [
        "23:24"
      ]
    },
    {
      "day": 5,
      "times": [
        "16:35"
      ]
    },
    {
      "day": 1,
      "times": [
        "17:45"
      ]
    }
  ],
  "app_ids_include": [
    35593,
    35897,
    37613,
    37809,
    39026,
    33337
  ],
  "app_ids_exclude": null,
  "app_groups_include": [
    35593,
    35897,
    37613,
    37809,
    39026,
    33337
  ],
  "app_groups_exclude": [],
  "all_apps": false,
  "all_countries": false,
  "all_languages": false,
  "all_audiences": true,
  "countries_include": null,
  "countries_exclude": null,
  "languages_include": null,
  "languages_exclude": null,
  "event_include": "install",
  "event_exclude": "",
  "audiences_include": null,
  "audiences_exclude": null,
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "external_ids": null,
  "archived": false
}
```

***

## List Smart weekly Schedulers

**GET** `https://core.push.express/api/b/v2/sched/smartweekly`

* **Description**: Returns a paginated list of smart weekly sending schedulers for the current user.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly?page=1&per_page=20"
    --request GET
    --header "Authorization: Bearer ..."
```

### Query params:

* `archived`, *optional, bool*. Filter by archived status. `true` — return only archived schedulers, `false` (default) — return only active.
* `page`, *optional, int*. Page number, default `1`.
* `per_page`, *optional, int*. Items per page, default `20`.
* `name`, *optional, string*. Filter by scheduler name (partial match).
* `app_id`, *optional, string*. Filter schedulers that include a specific application ID.
* `country`, *optional, string*. Filter by country code (iso3166\_1\_alpha2).
* `language`, *optional, string*. Filter by language code (bcp47).
* `sort_by`, *optional, string*. Field to sort by.
* `sort_dir`, *optional, string*. Sort direction: `asc` or `desc`.

### Response:

* 200

```json
{
  "schedulers": [
    {
      "id": 18998,
      "name": "Test Web push",
      "time_send": [
        {"day": 1, "times": ["17:45"]},
        {"day": 3, "times": ["15:06"]}
      ],
      "local_time": true,
      "status": true,
      "app_ids_include": [33337, 35593],
      "app_ids_exclude": null,
      "app_groups_include": [],
      "app_groups_exclude": [],
      "all_apps": false,
      "all_countries": false,
      "all_languages": false,
      "all_audiences": true,
      "countries_include": null,
      "countries_exclude": null,
      "languages_include": null,
      "languages_exclude": null,
      "event_include": ["install"],
      "event_exclude": [],
      "audiences_include": null,
      "audiences_exclude": null,
      "audience_groups_include": [],
      "audience_groups_exclude": [],
      "external_ids": null
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total_pages": 2,
    "total_count": 31
  }
}
```

***

## Update a Smart weekly Scheduler

**PUT** `https://core.push.express/api/b/v2/sched/smartweekly/:sched_id`

* **Description**: Fully replaces an existing smart weekly scheduler with new data. The request body has the same structure as [Create a Smart weekly Scheduler](#create-a-smart-weekly-scheduler). All time\_send entries are replaced.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly/:sched_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Updated scheduler name",
  "time_send": [
    {"day": 1, "times": ["09:00", "18:00"]},
    {"day": 5, "times": ["12:00"]}
  ],
  "local_time": true,
  "all_apps": false,
  "app_ids_include": [123],
  "app_ids_exclude": [],
  "app_groups_include": [],
  "app_groups_exclude": [],
  "all_countries": true,
  "countries_include": [],
  "countries_exclude": [],
  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [],
  "all_audiences": false,
  "audiences_include": [],
  "audiences_exclude": [],
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "event_include": ["install"],
  "event_exclude": []
}'
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to update.

### Body params:

Same as [Create a Smart weekly Scheduler](#create-a-smart-weekly-scheduler).

### Response:

* 200: Scheduler successfully updated

```json
{"id": 18998}
```

* 404: Scheduler not found or doesn't belong to user

***

## Delete a Smart weekly Scheduler

**DELETE** `https://core.push.express/api/b/v2/sched/smartweekly/:sched_id`

* **Description**: Deletes a smart weekly scheduler along with all its attached pushes and their translations. This action cannot be undone.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly/:sched_id"
    --request DELETE
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to delete.

### Response:

* 204: Scheduler and all its pushes successfully deleted, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Activate a Smart weekly Scheduler

**POST** `https://core.push.express/api/b/v2/sched/smartweekly/:sched_id/activate`

* **Description**: Activates a smart weekly scheduler, enabling recurring sends at the configured times.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly/:sched_id/activate"
    --request POST
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to activate.

### Response:

* 204: Scheduler successfully activated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Deactivate a Smart weekly Scheduler

**POST** `https://core.push.express/api/b/v2/sched/smartweekly/:sched_id/deactivate`

* **Description**: Deactivates a smart weekly scheduler, pausing all recurring sends. Can be reactivated later.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/smartweekly/:sched_id/deactivate"
    --request POST
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to deactivate.

### Response:

* 204: Scheduler successfully deactivated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 403 is returned when access to resource for current user is denied.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes` returns 404 when provided `sched_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Auto-push sending

Version date: 22 May 2025

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Create a Auto-push Scheduler](#create-a-auto-push-scheduler)
* [Get a Auto-push Scheduler](#get-a-auto-push-scheduler)
* [List Auto-push Schedulers](#list-auto-push-schedulers)
* [Update a Auto-push Scheduler](#update-a-auto-push-scheduler)
* [Delete a Auto-push Scheduler](#delete-a-auto-push-scheduler)
* [Activate a Auto-push Scheduler](#activate-a-auto-push-scheduler)
* [Deactivate a Auto-push Scheduler](#deactivate-a-auto-push-scheduler)

***

## Create a Auto-push Scheduler

**POST** `https://core.push.express/api/b/v2/sched/conversion`

* **Description**: Creates a new auto-push sending. This type of sending is to send a push automatically according to the filters when receiving the events of this task. Scheduler allows tuning the group of receivers via filters. No sending happens if scheduler has no attached pushes.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Name of scheduler",

  "all_apps": false,
  "app_ids_include": [
    123,
    234
  ],
  "app_ids_exclude": [
    345
  ],
  "app_groups_include": [
    1
  ],
  "app_groups_exclude": null,

  "all_countries": false,
  "countries_include": [
    "PT",
    "GB"
  ],
  "countries_exclude": [],

  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [
    "pt"
  ],

  "all_audiences": false,
  "audiences_include": [
    "myaud1_1234",
    "myaud2_1234"
  ],
  "audiences_exclude": [
    "myaud3_1234"
  ],
  "audience_groups_include": [
    11,
    22
  ],
  "audience_groups_exclude": [
    33
  ],

  "event_include": ["install","reg"],
  "event_exclude": ["dep"],
  "delay" : "5d10h30m"
}
```

### Body params:

part 1, name

* `name`, *required, string*. Name of scheduler.

part 2, filters, applications

* `all_apps`, *optional, bool*. Flag to include all applications into sending. If chosen True then `app_ids_include` and `app_groups_include` must be be omitted.
* `app_ids_include`, *optional, list of int*. List of application IDs to include into sending.
* `app_ids_exclude`, *optional, list of int*. List of application IDs to exclude from sending.
* `app_groups_include`, *optional, list of int*. List of application group IDs to include into sending. Make your application groups and manage them [here](https://app.push.express/app/group/)
* `app_groups_exclude`, *optional, list of int*. List of application group IDs to exclude from sending.

> If filters result in an empty set of applications then no scheduler is created. Corresponding `_include` and `_exclude` lists must have no common elements.

part 3, filters, countries

* `all_countries`, *optional, bool*. Flag to include devices with any country into sending. If chosen True then `countries_include` must be be omitted.
* `countries_include`, *optional, list of int*. List of countries to include into sending. Follow iso3166\_1\_alpha2 standard.
* `countries_exclude`, *optional, list of int*. List of countries to exclude from sending. Should be provided only if `all_countries` = true. Follow iso3166\_1\_alpha2 standard.

part 4, filters, languages

* `all_languages`, *optional, bool*. Flag to include devices with any language into sending. If chosen True then `languages_include` must be be omitted.
* `languages_include`, *optional, list of int*. List of languages to include into sending. Follow bcp47\_language\_tag standard.
* `languages_exclude`, *optional, list of int*. List of languages to exclude from sending. Should be provided only if `all_languages` = true. Follow bcp47\_language\_tag standard.

part 5, filters, audiences

In PushExpress audience is a tag that can be attached to devices. Some devices may have no audience.

> Create audiences or segments [here](https://app.push.express/postbacks/offers/), create audience or segment groups [here](https://app.push.express/postbacks/group/)

* `all_audiences`, *optional, bool*. Flag to include all audiences into sending.

If flag is on then only devices with any audience defined will be reached out while devices with no audience will not be reached out.

If one wants to reach out to all devices regardless of audience they should set `all_audiences` to false and omit `audiences_include` with `audience_groups_include`.

If chosen True then `audiences_include` and `audience_groups_include` must be be omitted.

* `audiences_include`, *optional, list of string*. List of audiences to include into sending.
* `audiences_exclude`, *optional, list of int*. List of audiences to exclude from sending.
* `audience_groups_include`, *optional, list of int*. List of audience group IDs to include into sending.
* `audience_groups_exclude`, *optional, list of int*. List of audience group IDs to exclude from sending.

> Corresponding `_include` and `_exclude` lists must have no common elements.

part 6, filters, events

Here you can specify both system events: install, reg, dep, and custom ones. The installation includes all devices, reg - only registered ones, dep - those who have replenished the balance at least once. Custom events can be created [here](https://docs.push.express/api/custom-events). More information about the events is [here](https://docs.push.express/quckstart/custom_events).

* `event_include`, *optional, list of string*. Filters devices which had this event in history. If field is not provided it will be set to `install` i.e. devices are to be chosen regardless of event happened.
* `event_exclude`, *optional, list of string*. Filters devices which did not have this event in history. If field not provided devices are to be chosen regardless of event that did not happen.

part 7, filters, delay

* `delay`, *optional, string*. The time after the event that the push will be sent. The 2d5h20m format. This means sending a push message two days, five hours, and twenty minutes after the event.

### Response:

* 201: New auto-push sending in future created

```json
{"id":12345}
```

* `id`, *int*. ID of newly created auto-push sending.

> No sending happens if scheduler has no attached pushes.

### Next step

To create push, refer to the [Push Creation API](/api/pushes#create-a-push).

To attach push to the scheduler, refer to the [Push Attachment API](/api/pushes#bind-pushes-to-a-scheduler).

***

## Get a Auto-push Scheduler

**GET** `https://core.push.express/api/b/v2/sched/conversion/:sched_id`

* **Description**: Retrieves details of a specific scheduler by its ID.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion/:sched_id"
    --request GET
    --header "Authorization: Bearer ..."
```

***

### Response

* 200

```json
{
  "id": 6841,
  "name": "автопуш рег",
  "app_ids_include": [
    33337,
    35593,
    35897,
    37613,
    37809,
    39026,
    40501
  ],
  "app_ids_exclude": null,
  "app_groups_include": [],
  "app_groups_exclude": [],
  "all_apps": false,
  "all_countries": false,
  "all_languages": false,
  "all_audiences": true,
  "countries_include": null,
  "countries_exclude": null,
  "languages_include": null,
  "languages_exclude": null,
  "event_include": "reg",
  "event_exclude": "",
  "audiences_include": null,
  "audiences_exclude": null,
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "external_ids": null,
  "delay": "1m",
  "archived": false
}
```

***

## List Auto-push Schedulers

**GET** `https://core.push.express/api/b/v2/sched/conversion`

* **Description**: Returns a paginated list of auto-push (conversion) schedulers for the current user.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion?page=1&per_page=20"
    --request GET
    --header "Authorization: Bearer ..."
```

### Query params:

* `archived`, *optional, bool*. Filter by archived status. `true` — return only archived schedulers, `false` (default) — return only active.
* `page`, *optional, int*. Page number, default `1`.
* `per_page`, *optional, int*. Items per page, default `20`.
* `name`, *optional, string*. Filter by scheduler name (partial match).
* `app_id`, *optional, string*. Filter schedulers that include a specific application ID.
* `country`, *optional, string*. Filter by country code (iso3166\_1\_alpha2).
* `language`, *optional, string*. Filter by language code (bcp47).
* `sort_by`, *optional, string*. Field to sort by.
* `sort_dir`, *optional, string*. Sort direction: `asc` or `desc`.

### Response:

* 200

```json
{
  "schedulers": [
    {
      "id": 6841,
      "name": "Auto-push reg",
      "delay": "1m",
      "status": true,
      "app_ids_include": [33337, 35593],
      "app_ids_exclude": null,
      "app_groups_include": [],
      "app_groups_exclude": [],
      "all_apps": false,
      "all_countries": false,
      "all_languages": false,
      "all_audiences": true,
      "countries_include": null,
      "countries_exclude": null,
      "languages_include": null,
      "languages_exclude": null,
      "event_include": ["reg"],
      "event_exclude": [],
      "audiences_include": null,
      "audiences_exclude": null,
      "audience_groups_include": [],
      "audience_groups_exclude": [],
      "external_ids": null
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total_pages": 1,
    "total_count": 5
  }
}
```

***

## Update a Auto-push Scheduler

**PUT** `https://core.push.express/api/b/v2/sched/conversion/:sched_id`

* **Description**: Fully replaces an existing auto-push scheduler with new data. The request body has the same structure as [Create a Auto-push Scheduler](#create-a-auto-push-scheduler).

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion/:sched_id"
    --request PUT
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
  "name": "Updated auto-push",
  "delay": "2h30m",
  "all_apps": false,
  "app_ids_include": [123, 234],
  "app_ids_exclude": [],
  "app_groups_include": [],
  "app_groups_exclude": [],
  "all_countries": true,
  "countries_include": [],
  "countries_exclude": [],
  "all_languages": true,
  "languages_include": [],
  "languages_exclude": [],
  "all_audiences": false,
  "audiences_include": ["myaud1_1234"],
  "audiences_exclude": [],
  "audience_groups_include": [],
  "audience_groups_exclude": [],
  "event_include": ["reg"],
  "event_exclude": []
}'
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to update.

### Body params:

Same as [Create a Auto-push Scheduler](#create-a-auto-push-scheduler).

### Response:

* 200: Scheduler successfully updated

```json
{"id": 6841}
```

* 404: Scheduler not found or doesn't belong to user

***

## Delete a Auto-push Scheduler

**DELETE** `https://core.push.express/api/b/v2/sched/conversion/:sched_id`

* **Description**: Deletes an auto-push scheduler along with all its attached pushes and their translations. This action cannot be undone.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion/:sched_id"
    --request DELETE
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to delete.

### Response:

* 204: Scheduler and all its pushes successfully deleted, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Activate a Auto-push Scheduler

**POST** `https://core.push.express/api/b/v2/sched/conversion/:sched_id/activate`

* **Description**: Activates an auto-push scheduler. Once active, it will automatically send pushes when the configured events occur.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion/:sched_id/activate"
    --request POST
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to activate.

### Response:

* 204: Scheduler successfully activated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Deactivate a Auto-push Scheduler

**POST** `https://core.push.express/api/b/v2/sched/conversion/:sched_id/deactivate`

* **Description**: Deactivates an auto-push scheduler, stopping automatic sends on events. Can be reactivated later.

### Request

```bash
curl --url "https://core.push.express/api/b/v2/sched/conversion/:sched_id/deactivate"
    --request POST
    --header "Authorization: Bearer ..."
```

### URL params:

* `sched_id`, *required, int*. ID of the scheduler to deactivate.

### Response:

* 204: Scheduler successfully deactivated, no content returned
* 404: Scheduler not found or doesn't belong to user

***

## Error handling

All HTTP response codes 2xx SHOULD be considered as success. Requested action was executed successfully.

All HTTP response codes above 400 MUST be considered as error. Requested action failed. Retries policy should be hold according to HTTP specification.

Common API errors:

* 400 - request error. Request has invalid data. Check you request (url, headers, payload)
* 401 is returned when provided API token is invalid. Check your authentification data.
* 403 is returned when access to resource for current user is denied.
* 404 is returned when resource doesn't exist. Check your request data.

  Example: **PUT** `https://core.push.express/api/b/v2/sched/:sched_type/:sched_id/pushes` returns 404 when provided `sched_id` doesn't exist
* All HTTP response codes 5xx - other errors from proxy servers, load balancers, etc. There may or may not have some explanation in response body. These errors always require retries.

**API errors** have `content-type: application/json` header and json response body

Example: `{"error": "validation error: ...", "req_id":"<string>"}`

Response parameters:

* `req_id` *string* is a request ID. It is used by support for problem solving, please, provide it to support if problem emerged.
* `error` object, describing errors.

**Non-API errors** like 502, 504, etc., may or may not include a description.


# Custom events

Version date: 26 January 2026

***

> To authorize, you need to add the header `"Authorization: Bearer ..."` (get your API KEY in your personal account on "Personal" tab).

## Endpoints

* [Create a custom event](#create-a-custom-event)
* [List all custom events](#list-all-custom-events)
* [Get a custom event](#get-a-custom-event)
* [Update a custom event](#update-a-custom-event)
* [Delete a custom event](#delete-a-custom-event)
* [Activate a custom event](#activate-a-custom-event)
* [Deactivate a custom event](#deactivate-a-custom-event)

***

### Create a custom event

**POST** `https://core.push.express/api/b/v2/events`

* **Description**: Creates a new custom event.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events"
    --request POST
    --header "content-type: application/json"
    --header "Authorization: Bearer ..."
    --data '
{
    "name": "Trial event",
    "code": "trial"
}
'
```

### Body params:

* `name`, *required, string*. Name of event.
* `code`, *required, string*. The event code. It is needed to send the event to the PushExpress service. Must contain only alphanumeric characters and underscores.

### Response:

* 201: New event created

```json
{"id":12345}
```

* `id`, *int*. ID of newly created event.

***

### List all custom events

**GET** `https://core.push.express/api/b/v2/events`

* **Description**: Returns a list of all custom events for the authenticated user.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events" \
    --request GET \
    --header "Authorization: Bearer ..."
```

### Response:

* 200: Success

```json
{
"events": [
  {
    "id": 12345,
    "name": "Trial event",
    "code": "trial",
    "status": 1,
    "type": "CUSTOM"
  },
  {
    "id": 12346,
    "name": "Purchase event",
    "code": "purchase",
    "status": 1,
    "type": "CUSTOM"
  }
],
	"pagination": {
		"page": 1,
		"per_page": 50,
		"total_pages": 1,
		"total_count": 3
	}
}
```

* `id`, *int*. Event ID.
* `name`, *string*. Name of event.
* `code`, *string*. Event code.
* `status`, *int*. Event status (0 = inactive, 1 = active).
* `type`, *string*. Event type ("CUSTOM", "SYSTEM").

  **Pagination:**

Now supports one type of pagination:

1. **Page-based pagination** - for UI with numbered pages (limited to 10K records).

**Page-based**

Request

```bash
curl --url "https://core.push.express/api/b/v2/events?page=2&per_page=50"
    --request GET
    --header "Authorization: Bearer ..."
```

Query parameters:

* `page`, *int, optional*. Page number.
* `per_page`, *int, optional*. The number of entries per page.

````

Response:
```json
{
  "events": [
  {
    "id": 12345,
    "name": "Trial event",
    "code": "trial",
    "status": 1,
    "type": "CUSTOM"
  },
  {
    "id": 12346,
    "name": "Purchase event",
    "code": "purchase",
    "status": 1,
    "type": "CUSTOM"
  }
],
  "pagination": {
    "page": 2,
    "per_page": 50,
    "total_pages": 15,
    "total_count": 705
  }
}
````

* `pagination`, *object*. Pagination metadata.
* `pagination.page`, *int*. Current page number.
* `pagination.per_page`, *int*. Records per page.
* `pagination.total_pages`, *int*. Total number of pages.
* `pagination.total_count`, *int*. Total matching records.

**Limit:** Max offset = 10,000 records. UI with numbered pages \[1] \[2] \[3]. Small/medium datasets.

***

### Get a custom event

**GET** `https://core.push.express/api/b/v2/events/{id}`

* **Description**: Returns details of a specific custom event.

> **Note**: This endpoint works only for **CUSTOM** events created by the user. System events (`SYSTEM` type: `install`, `reg`, `dep`, etc.) appear in the list but return `404` when fetched by ID.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events/12345" \
    --request GET \
    --header "Authorization: Bearer ..."
```

### URL params:

* `id`, *required, int*. Event ID.

### Response:

* 200: Success

```json
{
  "id": 12345,
  "name": "Trial event",
  "code": "trial",
  "status": 1,
  "type": "CUSTOM"
}
```

* `id`, *int*. Event ID.
* `name`, *string*. Name of event.
* `code`, *string*. Event code.
* `status`, *int*. Event status (0 = inactive, 1 = active).
* `type`, *string*. Event type.
* 404: Event not found

***

### Update a custom event

**PUT** `https://core.push.express/api/b/v2/events/{id}`

* **Description**: Updates an existing custom event. Only name and code can be updated.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events/12345" \
    --request PUT \
    --header "content-type: application/json" \
    --header "Authorization: Bearer ..." \
    --data '
{
    "name": "Updated Trial event",
    "code": "trial_updated"
}
'
```

### URL params:

* `id`, *required, int*. Event ID.

### Body params:

* `name`, *required, string*. New name of event.
* `code`, *required, string*. New event code. Must contain only alphanumeric characters and underscores.

### Response:

* 200: Event updated

```json
{
  "id": 12345,
  "name": "Updated Trial event",
  "code": "trial_updated",
  "status": 1,
  "type": "CUSTOM"
}
```

* 404: Event not found
* 400: Invalid data or code already exists

***

### Delete a custom event

**DELETE** `https://core.push.express/api/b/v2/events/{id}`

* **Description**: Deletes a custom event permanently.

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events/12345" \
    --request DELETE \
    --header "Authorization: Bearer ..."
```

### URL params:

* `id`, *required, int*. Event ID.

### Response:

* 204: Event deleted successfully (no content)
* 404: Event not found

***

### Activate a custom event

**POST** `https://core.push.express/api/b/v2/events/{id}/activate`

* **Description**: Activates a custom event (sets status to 1).

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events/12345/activate" \
    --request POST \
    --header "Authorization: Bearer ..."
```

### URL params:

* `id`, *required, int*. Event ID.

### Response:

* 204: Event activated successfully (no content)
* 404: Event not found

***

### Deactivate a custom event

**POST** `https://core.push.express/api/b/v2/events/{id}/deactivate`

* **Description**: Deactivates a custom event (sets status to 0).

Request:

```bash
curl --url "https://core.push.express/api/b/v2/events/12345/deactivate" \
    --request POST \
    --header "Authorization: Bearer ..."
```

### URL params:

* `id`, *required, int*. Event ID.

### Response:

* 204: Event deactivated successfully (no content)
* 404: Event not found


# SDK API specs

**PushExpress -- SDK and application instances API reference**

Version date: 05 Feb 2024

***

## Application flow

### 1. Application startup

Clients (app developers) MUST do some initialization in application code (see [SDK interface](#sdk-interface)).

Usually, they should provide `app_id` (which MUST be stored in app local storage) and SDK flow `activation flag` (just in-memory flag).

If no valid `app_id` found, or SDK flow is not activated by developer, further steps SHALL NOT be done.

#### 1.1. Generate some local-persistent installation-token (`ic_token`)

Clients need to have some independent globally-unique installation id, even if no internet access available.

This id often used in multiple ad-platforms, and very useful for clients who do not have their own user base with their own ids.

We suggest to use UUIDv4 or UUIDv7 as easily accessible on any modern programming languages.

Of course, clients can generate and store this ID on their own, but it is more convenient to do this at the SDK level.

So, you need:

1. Generate UUIDv4 (or UUIDv7) to use as `ic_token`
2. Store `ic_token` in local storage.

```kotlin
import java.util.UUID

const val icToken = UUID.randomUUID().toString()

// store icToken in app restart-persistent local storage!!!
```

#### 1.2. Check if it is first start after installation

Access some variable from app persistent local storage, for example `ic_id` (PushExpress App Instance Id).

If non-nil and non-empty variable found, assume it is existing install and go to section **1.3.**

Else, do the following steps:

1. Get `ic_id` as specified here: [get id for new app instance](##get-id-for-new-app-instance)
2. In case of any error (non-200 response or invalid response object), retry *infinitely* with exponential backoff algorithm, better with random initial value between 1-5s, capped at maximum delay of about 120s
3. Store aquired `ic_id` in local storage. Storage MUST be resistant to app updates and MUST be cleared on app removal. Storage SHOULD not overlap with existing app storages (for use as library). If it is not possible, use a more distinguishable field name (`px_ic_id`)
4. Go to section **2.**

#### 1.3. Startup on existing installation

As soon as possible after app startup issue first update app info request as described in section **2.**

Periodically update app info with received `update_interval_sec` interval.

### 2. Update app info

1. Gather some device and app info params like FCM token, country, language, timezone, app onscreen count and time, etc
2. Issue [update app info](##update-app-instance-info) request (only some fields are required, other can be updated later)
3. In case of any error (non-200 response or invalid response object), retry *infinitely* with exponential backoff algorithm, better with random initial value between 1-5s, capped at maximum delay of about 120s
4. Repeat [update app info](##update-app-instance-info) request with received `update_interval_sec` interval

Note: it's better to update current app state (on screen/background) and send actual `onscreen_count` and `onscreen_sec`.

### 3. Subscribe and listen transport messages

Subscribe for transport channel (for example, Firebase Cloud Messaging).

#### 3.1. Firebase Cloud Messaging transport

Incoming message id MUST be registered by extracting data field `px.msg_id` and [send it](##notification-events) in `clicked` event.

Notification MUST be displayed with title, body, image and icon, based on standard Firebase `notification` fields. Or manually by corresponding `px.*` fields in `data`.

After notification has been displayed, `delivered` event [SHOULD be send](##notification-events). If not possible to detect displaying, send it after FCM message has been received.

FCM transport message format:

```json
{
  "notification": {
    "title": "hello there",
    "body": "hello",
    "image": "https://avatars.githubusercontent.com/u/108216418",
    "icon": "https://avatars.githubusercontent.com/u/108216418"
  },
  "data": {
    "px.msg_id": "1828471_1721.7f288d0f-a026-4960-aa04-0c834f07a3ea.5",
    "px.title": "hello there",
    "px.body": "hello",
    "px.image": "https://avatars.githubusercontent.com/u/108216418",
    "px.icon": "https://avatars.githubusercontent.com/u/108216418"
  }
}
```

### 4. Gather app lifecycle (on screen, background) events

Application startups and screen time SHOULD be tracked and saved in local storage, for sending correct `onscreen_count` and `onscreen_sec` fields in [update app info](##update-app-instance-info) request.

App [lifecycle events](##lifecycle-events) MAY be sent when app is shown on screen or goes to background.

## SDK interface

Below is an example of Kotlin SDK interface:

```kotlin
import com.pushexpress.sdk.main.SdkPushExpress

const val PUSHEXPRESS_APP_ID = "####-######"

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        SdkPushExpress.initialize(PUSHEXPRESS_APP_ID)
        SdkPushExpress.setExternalId("user_registration_id_1234") // optional
        SdkPushExpress.setTag("ad_id", getAdvertisingIdOrEmpty()) // optional
        SdkPushExpress.setTag("offer", "google_ads_12321") // optional
        SdkPushExpress.activate() // Don't forget to activate SDK workflow!
}
```

## API v2 reference

### Get id for new app instance

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances" \
     --data '
{
    "ic_token": "212604ab-4b19-4d24-b8ec-3b8bac401a6a",
    "ext_id": ""
}
'
```

Body params:

* `ic_token`, *required*. Globally unique, local-generated app instance installation token, as described in section **1.1.**
* `ext_id`, *optional*. External id (for login/logout functionality).

Response:

* 200: New app instance created

  ```json
  {
    "id": "737112",
    "just_created": true
  }
  ```

  * `id`, *string*. Created app instance id (use as `:ic_id` in update info requests)
  * `just_created`, *bool*. Logged in to existing instance (via `ext_id`) or created new device.

### Update app instance info

Request:

```bash
curl --header "content-type: application/json" \
     --request PUT \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/info" \
     --data '
{
    "transport_type": "fcm",
    "transport_token": "dmC6c1abSiaSszJTAGhDpr:APA91bG..._TelL7kq8H0FN-NfKGSd",
    "platform_type": "android",
    "platform_name": "android_api_33",
    "ext_id": "<external_user_id|ic_token>",
    "lang": "en",
    "country": "BR",
    "tz_sec": -3600,
    "tags": { "offer": "google_ads_12321" }
}
'
```

Body params:

* `transport_type`, *required*. Push transport type. *One of `fcm`, `onesignal`, `huawei`, `apns`*
* `transport_token`, *optional*. `transport_type` specific token for receiving push
* `platform_type`, *required*. Notification rendering platform. *One of `android`, `ios`, `browser`*
* `platform_name`, *optional*. Readable platform name and version, like, `android_api_33` or `chrome_118`
* `country`, *recommended*. Sim card, locale or some other platform-based country code.

  *Accepts ISO3166-1, alpha-2 country code.*

  MUST NOT be determined by current network IP, better send nothing!

  Example (don't forget to check exceptions and return empty string on any):

  ```kotlin
  val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
  return tm.simCountryIso.takeIf { it.isNotEmpty() } ?: tm.networkCountryIso.takeIf { it.isNotEmpty() }
  ```
* `lang`, *highly recommended*. Platform-based language. *Accepts BCP 47 language tag*

  Example: `Locale.getDefault().language`
* `tz_sec`, *recommended*. Platform timezone east-shift from UTC-0, in seconds, like `3600`, or `-7200`. Example: `TimeZone.getDefault().rawOffset / 1000`
* `tags`, *optional*. User-specified string tags, map of strings with string keys. Predefined tag names: `ad_id`, `offer`, `webmaster`
* `onscreen_count`, *optional*. How many times app was opened and showed on screen (how many times the app was used)
* `onscreen_sec`, *optional*. How many seconds app was on screen (how much time the app was used)
* `agent_name`, *recommended*. Client agent (or SDK) name and version, like `my_sdk_0.0.0`

Response:

* 200: App instance info updated

  ```json
  {
    "id": "737112",
    "update_interval_sec": 120
  }
  ```

  * `id`, *string*. App instance id
  * `update_interval_sec`, *int*. Interval for sending update info requests in case of success

### Notification events

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/notification" \
     --data '
{
    "msg_id": "1828471_1721.7f288d0f-a026-4960-aa04-0c834f07a3ea.5",
    "event": "clicked"
}
'
```

Body params:

* `msg_id`, *required*. Message id (for FCM transport, get from `px.msg_id` data filed)
* `event`, *required*. Event name, one of:
  * `delivered` -- if possible, send after the notification has been successfully displayed to the user. Else, after the push message has been received from transport. Or do not send at all on any difficulties
  * `clicked` -- MUST be sent after the user clicks on the notification

Response:

* 202 Accepted, with no content type and empty body

### Lifecycle events

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/lifecycle" \
     --data '
{
    "event": "onscreen"
}
'
```

Body params:

* `event`, *required*. Event name, one of:
  * `onscreen` -- app opened (appeared on the screen)
  * `background` -- app gone to background (disappeared from screen)
  * `closed` -- app closed (if can be detected)

Response:

* 202 Accepted, with no content type and empty body

### Error handling

All HTTP response codes above 400 MUST be considered as error.

In general, errors do not require attention and do not need to be handled in any way.

There are two types of errors.

* Backend errors, will always have `content-type: application/json` header and json response body

  Example: `{"message": "validation error: ..."}`
* Other errors from proxy servers, load balancers, etc. There may or may not be some explanation in response body.

  Example: HTTP 504 Gateway Timeout, with empty response body


# SDK API v2

## Get id for new app instance

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances" \
     --data '
{
    "ic_token": "212604ab-4b19-4d24-b8ec-3b8bac401a6a",
    "ext_id": ""
}
'
```

Body params:

* `ic_token`, *required*. Globally unique, local-generated app instance installation token, as described in section **1.1.**
* `ext_id`, *optional*. External id (for login/logout functionality).

Response:

* 200: New app instance created

  ```json
  {
    "id": "737112",
    "just_created": true
  }
  ```

  * `id`, *string*. Created app instance id (use as `:ic_id` in update info requests)
  * `just_created`, *bool*. Logged in to existing instance (via `ext_id`) or created new device.

## Update app instance info

Request:

```bash
curl --header "content-type: application/json" \
     --request PUT \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/info" \
     --data '
{
    "transport_type": "fcm",
    "transport_token": "dmC6c1abSiaSszJTAGhDpr:APA91bG..._TelL7kq8H0FN-NfKGSd",
    "platform_type": "android",
    "platform_name": "android_api_33",
    "ext_id": "<external_user_id|ic_token>",
    "lang": "en",
    "country": "BR",
    "tz_sec": -3600,
    "tags": { "offer": "google_ads_12321" }
}
'
```

Body params:

* `transport_type`, *required*. Push transport type. *One of `fcm`, `onesignal`, `huawei`, `apns`*
* `transport_token`, *optional*. `transport_type` specific token for receiving push
* `platform_type`, *required*. Notification rendering platform. *One of `android`, `ios`, `browser`*
* `platform_name`, *optional*. Readable platform name and version, like, `android_api_33` or `chrome_118`
* `country`, *recommended*. Sim card, locale or some other platform-based country code.

  *Accepts ISO3166-1, alpha-2 country code.*

  MUST NOT be determined by current network IP, better send nothing!

  Example (don't forget to check exceptions and return empty string on any):

  ```kotlin
  val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
  return tm.simCountryIso.takeIf { it.isNotEmpty() } ?: tm.networkCountryIso.takeIf { it.isNotEmpty() }
  ```
* `lang`, *highly recommended*. Platform-based language. *Accepts BCP 47 language tag*

  Example: `Locale.getDefault().language`
* `tz_sec`, *recommended*. Platform timezone east-shift from UTC-0, in seconds, like `3600`, or `-7200`. Example: `TimeZone.getDefault().rawOffset / 1000`
* `tags`, *optional*. User-specified string tags, map of strings with string keys. Predefined tag names: `ad_id`, `offer`, `webmaster`
* `onscreen_count`, *optional*. How many times app was opened and showed on screen (how many times the app was used)
* `onscreen_sec`, *optional*. How many seconds app was on screen (how much time the app was used)
* `agent_name`, *recommended*. Client agent (or SDK) name and version, like `my_sdk_0.0.0`

Response:

* 200: App instance info updated

  ```json
  {
    "id": "737112",
    "update_interval_sec": 120
  }
  ```

  * `id`, *string*. App instance id
  * `update_interval_sec`, *int*. Interval for sending update info requests in case of success

## Notification events

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/notification" \
     --data '
{
    "msg_id": "1828471_1721.7f288d0f-a026-4960-aa04-0c834f07a3ea.5",
    "event": "clicked"
}
'
```

Body params:

* `msg_id`, *required*. Message id (for FCM transport, get from `px.msg_id` data filed)
* `event`, *required*. Event name, one of:
  * `delivered` -- if possible, send after the notification has been successfully displayed to the user. Else, after the push message has been received from transport. Or do not send at all on any difficulties
  * `clicked` -- MUST be sent after the user clicks on the notification

Response:

* 202 Accepted, with no content type and empty body

## Lifecycle events

Request:

```bash
curl --header "content-type: application/json" \
     --request POST \
     --url "https://core.push.express/api/r/v2/apps/:app_id/instances/:ic_id/events/lifecycle" \
     --data '
{
    "event": "onscreen"
}
'
```

Body params:

* `event`, *required*. Event name, one of:
  * `onscreen` -- app opened (appeared on the screen)
  * `background` -- app gone to background (disappeared from screen)
  * `closed` -- app closed (if can be detected)

Response:

* 202 Accepted, with no content type and empty body

## Error handling

All HTTP response codes above 400 MUST be considered as error.

In general, errors do not require attention and do not need to be handled in any way.

There are two types of errors.

* Backend errors, will always have `content-type: application/json` header and json response body

  Example: `{"message": "validation error: ..."}`
* Other errors from proxy servers, load balancers, etc. There may or may not be some explanation in response body.

  Example: HTTP 504 Gateway Timeout, with empty response body


