# Introduction

Experience native iOS and Android mobile apps in your browser with Appetize - no downloads, plugins, or extra permissions required!

Appetize is a cloud-based platform that allows developers and organizations to run and test their mobile apps on a virtual mobile device directly from their web browser.

Our platform is versatile and user-friendly, making it a popular choice for a wide range of scenarios, including:

* [Live App Previews](https://appetize.io/use-cases/live-app-previews)
* [Embedded Previews](https://appetize.io/use-cases/embedded-previews)
* [Mobile App Support](https://appetize.io/use-cases/mobile-app-support)
* [Training & Onboarding](https://appetize.io/use-cases/training-onboarding)
* [Mobile Demos](https://appetize.io/use-cases/mobile-demos)
* [Mobile Testing](https://appetize.io/use-cases/mobile-app-testing)
* [Screenshot Automation](https://appetize.io/use-cases/screenshot-automation)

and much more!

Ready to experience Appetize for yourself? You can get started in just a few clicks by uploading your own app or trying out our [online demo](https://appetize.io/demo).

## Upload your first App

The first thing to do at Appetize is upload your app. Follow our steps at:

{% content-ref url="/pages/TVnZCpOYcEDHfVd5fRgC" %}
[Uploading Apps](/platform/app-management/uploading-apps)
{% endcontent-ref %}

## Run, Embed & Share your App

Once you have your app uploaded, you can follow our steps at

{% content-ref url="/pages/TYzSVa2odyVgVQhojdUx" %}
[Running Apps](/platform/app-management/running-apps)
{% endcontent-ref %}

and/or

{% content-ref url="/pages/rDo8qcRuvyooVNLxjqwb" %}
[Embedding](/platform/embedding-apps)
{% endcontent-ref %}

{% content-ref url="/pages/aQU2X92M0BDRXUO4LW5Z" %}
[Sharing](/platform/sharing-apps)
{% endcontent-ref %}

to view your application in action.

## Customize your experience

Now that your app is up and running on Appetize, it's time to take control of your app experience. Check out all our[ features](/features) and customization options:

{% content-ref url="/pages/AGhvaEDiB3fAjhNWhfUF" %}
[Query Params Reference](/platform/query-params-reference)
{% endcontent-ref %}

{% content-ref url="/pages/1A6RAIgSaGMPuoibBq7d" %}
[JavaScript SDK](/javascript-sdk)
{% endcontent-ref %}


# Platform

Master the Appetize platform with our guides covering App Management, Device Sandbox, Embedding, Sharing, Session Inactivity timeouts and custom configurations using Query Parameters.

## Learn more about

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>App Management</td><td><a href="/files/KIa5ahJLvjXDQmMGMLhT">/files/KIa5ahJLvjXDQmMGMLhT</a></td><td></td><td></td><td><a href="/pages/XUiC1HOUtZH7H6haOVPE">/pages/XUiC1HOUtZH7H6haOVPE</a></td></tr><tr><td>Device Sandbox</td><td><a href="/files/8K3e9WIihllZX0p9TM30">/files/8K3e9WIihllZX0p9TM30</a></td><td></td><td></td><td><a href="/pages/i6CrJ7X9sV0wGY5BJhBA">/pages/i6CrJ7X9sV0wGY5BJhBA</a></td></tr><tr><td>Embedding</td><td><a href="/files/3Cy2yRbtqJKvrZEawovO">/files/3Cy2yRbtqJKvrZEawovO</a></td><td></td><td></td><td><a href="/pages/rDo8qcRuvyooVNLxjqwb">/pages/rDo8qcRuvyooVNLxjqwb</a></td></tr><tr><td>Sharing</td><td><a href="/files/Inm2umAHPqRHE1nGoEGy">/files/Inm2umAHPqRHE1nGoEGy</a></td><td></td><td></td><td><a href="/pages/aQU2X92M0BDRXUO4LW5Z">/pages/aQU2X92M0BDRXUO4LW5Z</a></td></tr><tr><td>Session Inactivity Timeout</td><td><a href="/files/7yMHwzUZfrgItyM2ppo3">/files/7yMHwzUZfrgItyM2ppo3</a></td><td></td><td></td><td><a href="/pages/MzLJjG1P0pZcJsjlmx2t">/pages/MzLJjG1P0pZcJsjlmx2t</a></td></tr><tr><td>Query Params Reference</td><td><a href="/files/NXCRy6RZOus8BENWa3Uw">/files/NXCRy6RZOus8BENWa3Uw</a></td><td></td><td></td><td><a href="/pages/AGhvaEDiB3fAjhNWhfUF">/pages/AGhvaEDiB3fAjhNWhfUF</a></td></tr></tbody></table>


# App Management

Easily manage your apps and builds on Appetize by following the guides below

## Learn more about

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Uploading Apps</td><td><a href="/pages/TVnZCpOYcEDHfVd5fRgC">/pages/TVnZCpOYcEDHfVd5fRgC</a></td><td></td><td></td><td><a href="/files/wRxYpNYd2scWfCFBPIaD">/files/wRxYpNYd2scWfCFBPIaD</a></td></tr><tr><td>App Dashboard</td><td><a href="/pages/nhQwczROZtoIsKCzbPI2">/pages/nhQwczROZtoIsKCzbPI2</a></td><td></td><td></td><td><a href="/files/81kHsNClp97srAb5YGA3">/files/81kHsNClp97srAb5YGA3</a></td></tr><tr><td>Running Apps</td><td><a href="/pages/TYzSVa2odyVgVQhojdUx">/pages/TYzSVa2odyVgVQhojdUx</a></td><td></td><td></td><td><a href="/files/Goge5EFJnrXl0DUJVL28">/files/Goge5EFJnrXl0DUJVL28</a></td></tr><tr><td>App Permissions</td><td><a href="/pages/wxvx657b3flrbJMnrVMW">/pages/wxvx657b3flrbJMnrVMW</a></td><td></td><td></td><td><a href="/files/WNAFPIe493ztFKyHXl31">/files/WNAFPIe493ztFKyHXl31</a></td></tr></tbody></table>


# Uploading Apps

Effortlessly upload your mobile app on Appetize within minutes, and start using it across various devices and operating systems.

## Preparing your Build

{% content-ref url="/pages/Wxt6qmFh599hLcX3LCti" %}
[Android](/platform/app-management/uploading-apps/android)
{% endcontent-ref %}

{% content-ref url="/pages/FZeK3GH0fupkeBe2VOZ0" %}
[iOS](/platform/app-management/uploading-apps/ios)
{% endcontent-ref %}

{% hint style="info" %}
We support cross-platform apps made in Flutter, Xamarin, React-Native, Kotlin Multiplatform and others which generate`.app` or `.apk` builds.

For common issues and troubleshooting, visit our [Knowledge base](https://support.appetize.io/uploading-and-installing-apps).
{% endhint %}

## Upload your App

### With Upload Page

To upload your application via a web browser use our [Upload](https://appetize.io/upload) dialog:

{% embed url="<https://appetize.io/upload>" %}

#### Update existing Apps

To update an existing app, follow the same steps as uploading a new app via the [Upload](https://appetize.io/upload) dialog. The [latest build](#user-content-fn-1)[^1] will automatically be associated with your app.

To view all builds for an app, select the app and go to the [App Builds Page](/platform/app-management/listing-apps#app-builds-page).

{% hint style="warning" %}
In some instances, you might want to explicitly update a particular app build. To do so, follow these steps:

1. Go to the [Apps Dashboard](https://appetize.io/apps).
2. **Select the app**.
3. **Search and select the build** you want to update.
4. Go to **Settings**.
5. Navigate to "**Update build**"

![](/files/hxY2FWAtWioY4aQ6Cyux)
{% endhint %}

### With CI/CD and Third-Party Integrations

Appetize integrates with several popular CI/CD tools and other third-party services, allowing you to:

* **Ensure Your App is Always Up-To-Date:**\
  Automatically upload the latest builds with one of our many CI/CD integrations.
* **Eliminate Manual Processes:**\
  Automate rolling out of updated apps or specific builds, saving time and effort.
* **Improve User and Development Workflows:**\
  Integrate with tools such as Storybook Native and Expo to improve user and developer experiences.
* **Run Tests on Pull Requests:**\
  Automatically upload and test builds on Pull Requests for efficient quality assurance.

Available integrations include:

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Github Actions</td><td><a href="https://github.com/appetizeio/github-action-appetize">https://github.com/appetizeio/github-action-appetize</a></td><td></td><td></td><td><a href="/files/jszSqSvvk24Tht8V0zNu">/files/jszSqSvvk24Tht8V0zNu</a></td></tr><tr><td>Gitlab</td><td><a href="https://about.gitlab.com/blog/2020/05/06/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/">https://about.gitlab.com/blog/2020/05/06/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/</a></td><td></td><td></td><td><a href="/files/tKjE9FrQrP3fq7AvWNYh">/files/tKjE9FrQrP3fq7AvWNYh</a></td></tr><tr><td>Bitrise</td><td><a href="https://bitrise.io/integrations/steps/appetize-deploy">https://bitrise.io/integrations/steps/appetize-deploy</a></td><td></td><td></td><td><a href="/files/edT4nUuAiKFi6dlNQYAe">/files/edT4nUuAiKFi6dlNQYAe</a></td></tr><tr><td>Expo</td><td><a href="https://expo.dev/">https://expo.dev/</a></td><td></td><td></td><td><a href="/files/BLghHCSwSZizyydv4Ra3">/files/BLghHCSwSZizyydv4Ra3</a></td></tr><tr><td>Fastlane</td><td><a href="https://docs.fastlane.tools/actions/appetize/">https://docs.fastlane.tools/actions/appetize/</a></td><td></td><td></td><td><a href="/files/NOfjYyiBWbiFSG9xpoL9">/files/NOfjYyiBWbiFSG9xpoL9</a></td></tr><tr><td>Storybook native</td><td><a href="https://github.com/storybookjs/native">https://github.com/storybookjs/native</a></td><td></td><td></td><td><a href="/files/WYhovYIJ8ArRednfptUH">/files/WYhovYIJ8ArRednfptUH</a></td></tr></tbody></table>

{% hint style="info" %}
If there is a 3rd party integration that you think should be on this list, please let us [know](mailto:support@appetize.io)!
{% endhint %}

### With REST API

Appetize also supports uploading your application programmatically by making use of our [REST API](/rest-api):

{% content-ref url="/pages/-MJVCLk7v1u3m2wZB2kx" %}
[Create new app](/rest-api/v1/create-new-app)
{% endcontent-ref %}

{% content-ref url="/pages/-MJVCPn0365TfTav5CE1" %}
[Update existing app](/rest-api/v1/update-existing-app)
{% endcontent-ref %}

[^1]: The **latest build** refers to the most recent version of your app on Android, identified by the [versionCode](https://developer.android.com/studio/publish/versioning#versioningsettings), and on iOS, by the [CFBundleShortVersionString](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) and [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).


# Android

To get started, Appetize requires the APK (or APKs) containing your application.

## Finding your APK file

### With Android Studio

Select **Build** -> **Build APK(s)** -> **Build APK(s)** or **Build** -> **Generated Signed APK** (and following the prompts)

<figure><img src="/files/yniOHPByJMEmMmQEdXlk" alt="" width="563"><figcaption><p>Building with Android Studio</p></figcaption></figure>

Once the build is complete you can locate the `.apk` file by selecting `locate` in the dialog that appears

<figure><img src="/files/6uooErBbokZMyQudCXL2" alt="" width="368"><figcaption><p>Select <code>locate</code> in the dialog to navigate to the apk</p></figcaption></figure>

or by navigating to

```
{project name}/{app module name}/build/outputs/apk/
```

### With Gradle

Generate your build with `gradle` by running the `assemble` command for your preferred app build variant e.g. `debug` variant

```
./gradlew assembleDebug
```

Once the build is complete you can locate the `.apk` file by navigating to

```
{project name}/{app module name}/build/outputs/apk/
```

## Converting AAB to APK or APKs

Appetize currently supports the `.apk` format and also accepts `.apks` (APK Set archive) files for upload. In order to get your application to work with Appetize you will need to convert your `aab` to an `apk`  or `apks` by making use of the [`bundletool`](https://developer.android.com/tools/bundletool) provided by Google.

#### Generate Universal APK Set archive

```shell-session
bundletool build-apks --bundle=/<your app>/{aab name}.aab \
    --output=/{your app}/{app name}.apks \
    --mode=universal
```

#### Generate Single APK file from the Universal APKs (optional)

```
unzip -p /{your app}/{app name}.apks universal.apk > /{your app}/{app name}.apk
```

## Troubleshooting

If you are having trouble running your uploaded Android app in Appetize, we recommend trying to run the same APK on the standard Google-provided Android emulator locally over ADB.

Once your emulator is launched and available via `adb devices`, you can install it using the `install` command:

```
adb install -r {your app}.apk
```

or by simply dragging over the `apk` into the emulator window.

Also check our [Knowledge Base](https://support.appetize.io/) for frequently asked questions and solutions.


# iOS

To get started, Appetize requires a .zip or .tar.gz file containing your compressed .app bundle.

Appetize currently only supports iOS Simulator builds (**.app**). A simulator build can be run in the iOS Simulator with Xcode. AppStore distribution device builds (**.ipa**) are not currently supported.

{% hint style="info" %}
For optimal performance and compatibility, we recommend uploading ARM simulator builds of your iOS app.&#x20;
{% endhint %}

## Finding your .app file

### With Xcode

The easiest way to get the iOS Simulator build is to run and build your application in Xcode while targeting an iOS Simulator

<figure><img src="/files/K7kToSg2v7KTWCRFGfG3" alt=""><figcaption><p>Build and run your application from Xcode while targeting a simulator</p></figcaption></figure>

Once the build is complete and the app is running in the simulator, you can locate the `.app` file by navigating to **Product** -> **Show Build Folder in Finder** -> **Products/Debug-iphonesimulator**

<figure><img src="/files/kQiy3WMzrcjpSR4icBtS" alt="" width="265"><figcaption><p>Show build folder in Xcode</p></figcaption></figure>

### With Xcode Command Line Tools

You can also generate the iOS Simulator build of your app by building it directly via the command line using `xcodebuild`.

#### With .xcodeproj

{% code overflow="wrap" %}

```
xcodebuild -project '{project_name}.xcodeproj' \
 -scheme '{scheme_name}' \
 -sdk iphonesimulator \
 -configuration Debug
```

{% endcode %}

#### With .xcworkspace

{% code overflow="wrap" %}

```
xcodebuild -workspace '{your_workspace_name}.xcworkspace' \
 -scheme '{scheme_name}' \ 
 -sdk iphonesimulator \
 -configuration Debug
```

{% endcode %}

The `app` file can then be found under

```
build/Debug-iphonesimulator/
```

## Compress your .app file

Once you have located your `.app` file, Appetize requires it to be in a compressed `zip` or `tar.gz` file e.g.

```
zip -r {app name}.zip {app name}.app
```

## Troubleshooting

If you are having trouble running your uploaded iOS app in Appetize, we recommend trying to run the same app on an simulator provided by Apple in Xcode.

Once your simulator is launched, you can simply drag over the `app` into the simulator window.

Also check our [Knowledge Base](https://support.appetize.io/) for frequently asked questions and solutions.


# App Dashboard

Organize your apps effortlessly with Appetize's App Dashboard and Groups, enabling seamless app management and access.

{% hint style="info" %}
For our Enterprise customers we also provide [Custom Launch Pages](/features/custom-launch-pages). They can provide a simple bookmark-friendly page for your teams, only showing the applications they are interested in.
{% endhint %}

## Single Applications

{% hint style="info" %}
App builds hosted on Appetize are distinguished by their unique Application Identifier. On Android, this identifier corresponds to the [Application ID](https://developer.android.com/build/configure-app-module#set-application-id), while on iOS, it is known as the [Bundle Identifier](https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids).
{% endhint %}

### App Dashboard

The [App Dashboard](https://appetize.io/apps) on Appetize provides you with an overview of all your uploaded apps.

<figure><img src="/files/iH2dnaP5W03DdydwWoVq" alt=""><figcaption><p>Apps Dashboard</p></figcaption></figure>

The App Dashboard displays the [latest build](#user-content-fn-1)[^1] uploaded for a specific application.

From there, you can:

* **Search** by application name or app identifier
* **Filter** your applications by Device Type (Android or iOS)
* Navigate to the [**App Builds**](#app-builds-page) page, where you can see all the individual builds associated with your preferred application.
* **Play** the latest build associated with your preferred application.
* **Share** the latest build associated with your preferred application
* See any [**App Groups**](#grouped-applications) you might have created

### App Builds Page

The App Builds page provides an overview of all the builds that was uploaded for a particular application. These are sorted by version and build information as defined by the platform.

<figure><img src="/files/9KsfuQySEdGoOuKCJmqf" alt=""><figcaption><p>App Builds page</p></figcaption></figure>

From there, you can:

* See an overview of the [latest build](#user-content-fn-1)[^1] uploaded for this application
  * Easily **play**, **debug** or **share** this build
  * **Remove** this application
* **Search** for a specific build by associated metadata (e.g. version name, tag, notes and buildId)
* **Upload** a new build
* Navigate to an individual [App Build Page](#app-build-page)
* **Play** the latest build associated with your preferred application.
* **Share** the latest build of your preferred application
* **Favorite** your most used apps
* See any [**App Groups**](#grouped-applications) you might have created

### Build Page

The Build page provides an overview of a specific build that was uploaded to Appetize.

<figure><img src="/files/mJmors70KANY1SBDIGFU" alt=""><figcaption><p>App Build Page</p></figcaption></figure>

From there, you can:

* See the build identifier
* See a quick overview of the last played and uploaded dates
* See an overview of all the **build** associated **metadata**
* Add **tags** to easily identify this build
* Add a **note** to describe this build
* Easily **play**, **share** or **debug** this build

### Rest API

Appetize also supports retrieving a list of your uploaded app builds (or a single app build by **buildId**) by making use of our [REST API](/rest-api):

{% hint style="info" %}
Each uploaded app build has a uniquely assigned **buildId** (previously known as [**publicKey**](/platform/sharing-apps#public-key)**)**.

To get a specific App Build you can navigate to the [App Dashboard](https://appetize.io/apps) and open the [App Builds ](#app-builds-page)page for the preferred App. Here you will be able to select individual builds that you might want to share or customize.
{% endhint %}

{% content-ref url="/pages/-MJVCSphxbQOWOtXDpDb" %}
[List apps](/rest-api/v1/list-apps)
{% endcontent-ref %}

### Unknown Builds

If a build provided to Appetize cannot be correctly processed for any reason, it will be added to the Unknown Builds page. From there, you have the option to either reupload the build, delete it, or provide the necessary information in a [Support request](https://appetize.io/support-request) so our team can investigate further.

<div data-full-width="true"><figure><img src="/files/qEwG3e0k0M3btjkLOZWV" alt=""><figcaption><p>Unknown Builds Page</p></figcaption></figure></div>

## Grouped Applications

Appetize also supports grouping multiple applications that you would like to install simultaneously on the same device.

{% hint style="warning" %}
Simultaneous installation of applications with the same `App Identifier` is not possible due to conflict in identifiers.
{% endhint %}

You can view all your App Groups on the [App Group Dashboard](https://appetize.io/app-groups).

### Creating a new group

To create a new group, select the `Add group` button

<figure><img src="/files/2AtkEZ4qhIAkistipwLX" alt=""><figcaption><p>Add Group action button</p></figcaption></figure>

The new group dialog will appear, where you can:

* Enter the preferred name for the group
* Specify the associated platform of the group

<figure><img src="/files/0C37l48beQsbu7bDKcxs" alt=""><figcaption><p>Add Group Dialog</p></figcaption></figure>

A new group will be created with no applications attached

<figure><img src="/files/osyoMVgh25j7WjU8hcLP" alt=""><figcaption><p>New Group page</p></figcaption></figure>

#### Add Applications

To add applications to the group, follow these steps:

1. Select the **Add Group** action.
2. In the **Add App** modal, either:
   * Search for your application by name or application ID, or
   * Browse the list of available applications.
3. Select one or multiple applications to add to the group.

<figure><img src="/files/tmbVhQV2RqJiO9wChtHA" alt=""><figcaption><p>Example Add App modal</p></figcaption></figure>

By default, the latest build associated with the app will always be used:

<figure><img src="/files/DFTLbhbls8KG7kNWd8WJ" alt=""><figcaption><p>Example of an app added.</p></figcaption></figure>

To modify this, you can:

1. Click on the dropdown next to the build information (e.g., "Latest Build").
2. Specify a version and/or tags that match the build.
3. Choose a specific build or select the latest one from the dropdown menu.

<figure><img src="/files/6kCRotVOx945nEIwhnns" alt=""><figcaption><p>Specify which build of an application you want by version, tag or buildId</p></figcaption></figure>

#### Remove Applications

To remove any of the added applications, simply click on the trash bin icon next to the app and confirm in the dialog box that appears.

<figure><img src="/files/e5JPuMKkADi7hSitHlRO" alt=""><figcaption><p>Example Trash Bin Icon next to app</p></figcaption></figure>

<figure><img src="/files/BiqwCQaBwXa4efGqFuxf" alt=""><figcaption><p>Remove App Confirmation Dialog</p></figcaption></figure>

### Unknown builds

If a build filter specified in an App Group can no longer be found or resolved, this information will appear at the bottom of the App Group. You can then remove or adjust the filter to find a new matching build.

<figure><img src="/files/17Bjyeol056IIc4NueFl" alt="" width="563"><figcaption><p>App Build Filter not found example</p></figcaption></figure>

### Playing and embedding your App Group

Once you have added all the applications to the group, you can use the group in the same way that you would have used the individual applications (e.g. play, embed etc.). You can view all your App Groups on the [App Group Dashboard](https://appetize.io/app-groups).

<figure><img src="/files/KeIiNH1RWUGSSBNnrFfn" alt=""><figcaption><p> App Group</p></figcaption></figure>

[^1]: The **latest build** refers to the most recent version of your app on Android, identified by the [versionCode](https://developer.android.com/studio/publish/versioning#versioningsettings) , and on iOS, by the [CFBundleShortVersionString](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) and [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).


# Running Apps

Get a quick preview and seamless experience of your app across multiple devices and operating systems with Appetize's user-friendly App page.

## Features

<figure><img src="/files/zxfm3PrMWKbgeGBoHqNY" alt=""><figcaption><p>App page</p></figcaption></figure>

Appetize provides an out-of-the-box App page that provides easy access to:

* Different Supported [Devices and OS Versions](/features/devices-and-os-versions).
* Debugging features:
  * [Automation Recorder](/features/ui-automation)
  * [Network Traffic Monitor](/features/network-traffic-monitor)
  * [Debug Logs](/features/debug-logs)
  * [Adb Tunnel](/features/advanced-features/android/adb-tunnel) (Android Only)
* Device toolbar:

  * Home/Lock buttons
  * Biometry
  * Light/Dark mode
  * Rotate the device
  * Change Location
  * Switch between supported languages
  * Shake device
  * Font Scale
  * Add Media
  * Save screenshots
  * Toggle soft keyboard

  <figure><img src="/files/cKxzY15lMIAlFrFuvf6m" alt=""><figcaption><p>Device Toolbar</p></figcaption></figure>
* Support for all [Query Parameters](/platform/query-params-reference) to fully customize your experience.

<figure><img src="/files/IcS9BEA21MLFrgSWyvpO" alt=""><figcaption><p>Easily switch devices, enable debugging features and more.</p></figcaption></figure>

## Accessing your App Page

#### Latest Build

You can access the latest build associated with your app by making use of your app identifier and platform in the URL e.g.

{% code overflow="wrap" %}

```url
https://appetize.io/apps/{platform}/{appId}
```

{% endcode %}

Alternatively you can go to your [Apps](https://appetize.io/apps) page and click **play** on the app you want to run.

#### Specific Build

You can access a specific build of your app by making use of your buildId (or previously known as publicKey) in the URL e.g.

```
https://appetize.io/app/{buildId}
```

{% hint style="info" %}
If you are still using our v1 API, the buildId and the publicKey will be interchangeable e.g.

```
https://appetize.io/app/{publicKey|buildId}
```

{% endhint %}

Alternatively you can go to your [Apps](https://appetize.io/apps) page and select the App with which the build is associated. From here you can access the individual builds as explained [here](/platform/app-management/listing-apps#app-builds-page).


# App Permissions

With Appetize's app permissions, users can manage who has access to their app, can debug apps or view network traffic logs and more.

## Who can run your app

### App Identifier Link

See Sharing [**App Identifier Links**](/platform/sharing-apps#app-identifier).

By default, only users linked to your organization and signed in to Appetize will have access to your App Identifier link.

### Build Identifier Link

See Sharing [**Build Identifier Links**](/platform/sharing-apps#build-identifier).

By default, anybody who has your app's Build Identifier link, i.e. its **buildId** (previously known as **publicKey**), will have permission to run your app.

Some customers want to restrict access, so that only authenticated users may run their app. For this purpose, we have a configuration option which can be set at either the organization level or the app build level.

## Organization Session Default Permissions

Set and control default session permissions across your organization by navigating to [**Organization -> Session Defaults**](https://appetize.io/organization/session-defaults).

<figure><img src="/files/jXRxkJCtKgIMgwGFRmpE" alt=""><figcaption><p>Example Organization App Build Permissions</p></figcaption></figure>

## App Build Permissions

{% hint style="warning" %}
App Build Permissions will override Organization Session Default Permissions. We recommend using Organization Session Default Permissions. In the future we will introduce more granular ways to provide permissions for your apps.
{% endhint %}

For the app-level setting, navigate to your [**Dashboard**](https://appetize.io/dashboard), select your preferred app and then select the build you want to change permissions for:

<figure><img src="/files/KlkEwRBrCc95EBmq3MUd" alt=""><figcaption><p>Select the build you want to apply permission to</p></figcaption></figure>

and finally configure the option under Settings > App Permissions.

{% hint style="info" %}
App Level Permissions can also be applied when uploading the app via our REST API. See [Create new app](/rest-api/v1/create-new-app) and [Update existing app](/rest-api/v1/update-existing-app) for more information.
{% endhint %}

<figure><img src="/files/MfVMiJAkjlR2sH3HujS4" alt=""><figcaption><p>Example App Build Level Permissions</p></figcaption></figure>

## Permissions

Configure the permissions below to determine whether you need to be authenticated with Appetize and linked to the organization or simply have the Embed or View link for an app in order to perform the following actions.

| Permission                | Description                                                          |
| ------------------------- | -------------------------------------------------------------------- |
| **run**                   | Run your application                                                 |
| **networkProxy**          | Specify a network proxy when running the app                         |
| **networkIntercept**      | Use Appetize's intercepting proxy when running the app               |
| **debugLog**              | View your app's NSLog/Logger or Logcat output                        |
| **adbConnect**            | Debug your app by connecting ADB to the hosted emulator              |
| **androidPackageManager** | Allow the installation of additional APK's while your app is running |


# Device Sandbox

Appetize provides a device sandbox that launches our devices without any preinstalled apps, offering a clean testing environment for optimal web app evaluation and compatibility testing.

You can access your device sandbox by logging in and then opening your [**Device Sandbox**](https://appetize.io/app/standalone) page or by selecting **Device Sandbox** in the left-hand menu.

<figure><img src="/files/ZiOmgGcI41tbTuKu5k64" alt=""><figcaption><p>Select <code>Device Sandbox</code> in the left-hand menu.</p></figcaption></figure>


# Embedding

Enhance your user experience by embedding  Appetize virtual devices into your own website or product using Appetize's embedding functionality.

Many customers embed the Appetize virtual devices into their own websites and products. You may use an `iFrame` to embed your app into any HTML.

Our embed links support all our [Query Parameters](/platform/query-params-reference) to allow you to fully customize your experience.

## Where is my embed link?

The easiest way to find your embed link is by going to your [Apps](https://appetize.io/apps) or [App Builds](/platform/app-management/listing-apps#app-builds-page) page and selecting **share** under the app, App Group or build that you want to embed on your website.

<figure><img src="/files/aH0sPUouf1OLtnfG6rEn" alt=""><figcaption><p>Find your embed link by clicking the share button</p></figcaption></figure>

Currently, `Share` will share a specific build of your application. Soon, we will introduce new sharing features that will allow you to share the latest build without changing the URL.

<figure><img src="/files/62yPxlDPe2VTMQ6SEYna" alt="" width="386"><figcaption><p>Example Share dialog</p></figcaption></figure>

{% hint style="info" %}
You can customize and test out all the [Query Parameters](/platform/query-params-reference) on your [App Page](/platform/app-management/running-apps) before switching out the `app` link with an `embed` link.
{% endhint %}

## Sample Usage

After obtaining the embed link, you can easily embed the content into your website by creating an `iFrame`:

{% code overflow="wrap" %}

```html
<iframe
  src="https://appetize.io/embed/{buildId}"
  width="378px" 
  height="800px" 
  frameborder="0" 
  scrolling="no"></iframe>
```

{% endcode %}


# Sharing

Sharing your app on Appetize is easy! Simply upload your build and share the link with others for instant access on any device.

## Where is my share link?

The easiest way to find your external share link is by going to your [Apps](https://appetize.io/apps) or [App Builds](/platform/app-management/listing-apps#app-builds-page) page and selecting **share** under the app, App Group or build that you want to embed on your website.

<figure><img src="/files/aH0sPUouf1OLtnfG6rEn" alt=""><figcaption><p>Find your share link by clicking the share button</p></figcaption></figure>

Currently, `Share` will share a specific build of your application. Soon, we will introduce new sharing features that will allow you to share the latest build without changing the URL.

<figure><img src="/files/nbT9qrWW58EpQT0a5GyD" alt="" width="386"><figcaption><p>Example Share dialog</p></figcaption></figure>

## App Identifier

App builds hosted on Appetize are distinguished by their unique Application Identifier. On Android, this identifier corresponds to the [Application ID](https://developer.android.com/build/configure-app-module#set-application-id), while on iOS, it is known as the [Bundle Identifier](https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids).

You can access the [latest build](#user-content-fn-1)[^1] of a particular application like this:

```
https://appetize.io/apps/{platform}/{appId}
```

{% hint style="warning" %}
By default, only users linked to your organization and signed in to Appetize will have access to your App Identifier link.
{% endhint %}

This link can also be further filtered to a specific type of build by passing in the preferred metadata e.g.

* By tag `https://appetize.io/apps/{platform}/{appId}?tag=dev`
* By version `https://appetize.io/apps/{platform}/{appId}/{versionName}`

## Build Identifier

*Previously known as publicKey*

When you upload an app build to Appetize, you receive a link to view your app. This link includes the app's assigned **buildId**, and looks like this:

```
https://appetize.io/app/{buildId}
```

By default, anybody who has your app's build link, i.e. its **buildId** (previously known as **publicKey**), will have permission to run your app.

Your app link can be easily shared with whomever you'd like, or [embedded](/platform/embedding-apps) into your own applications.

{% hint style="info" %}
Some customers want to restrict access, so that only authenticated users may run their app (or certain features). See [App Build Permissions](/platform/app-management/app-permissions) for more information on how to do this.
{% endhint %}

[^1]: The **latest build** refers to the most recent version of your app on Android, identified by the [versionCode](https://developer.android.com/studio/publish/versioning#versioningsettings) , and on iOS, by the [CFBundleShortVersionString](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) and [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).


# Session Inactivity Timeout

With Appetize's Session Inactivity Timeout, users can manage how long their device's session stays active, before being freed up for reuse.

By **default**, your Appetize session will end after **2 minutes** of inactivity. You can modify the default app timeout, or specify a timeout for a specific app build by following the steps below:

## Default App Timeout

Navigate to [**Organization -> Session Defaults**](https://appetize.io/organization/session-defaults) **->** **Session Timeout**

<figure><img src="/files/C0ZgIDAGUyQxV1bsGSMG" alt=""><figcaption><p>Default App Session Timeout</p></figcaption></figure>

## App Build Specific Timeout

Navigate to your [**Apps Dashboard**](https://appetize.io/apps) **-> Select** your preferred **app -> Select the build** you want to modify **-> Settings -> Session timeout**

<figure><img src="/files/U1iZIvKZNUgYJfL3pWYk" alt=""><figcaption><p>Select the App you want to modify</p></figcaption></figure>

<figure><img src="/files/KlkEwRBrCc95EBmq3MUd" alt=""><figcaption><p>Select the build you want to modify</p></figcaption></figure>

<figure><img src="/files/FMZ71pt2e9ubQBV9Xgoo" alt=""><figcaption><p>Build Specific Inactivity Timeout</p></figcaption></figure>

{% hint style="info" %}
You can also[ set](/rest-api/v1/create-new-app)/[update](/rest-api/v1/update-existing-app) the inactivity timeout of your app with our [REST API](/rest-api) when uploading your app.
{% endhint %}

## What's the behavior if you reach your concurrency limit?

When a user launches Appetize and the maximum concurrency limit has been reached, they will be placed in a virtual 'queue' and wait until additional capacity becomes available:

<figure><img src="/files/qsCcLiyLYHVILgs7SwBJ" alt="" width="360"><figcaption><p>Sample of reaching the concurrency queue in our demo experience</p></figcaption></figure>

As soon as a device becomes available, the queued users will be granted access, allowing them to continue as usual.


# Query Params Reference

With query parameters on Appetize, users can easily switch between different device and operating system versions, languages, and many other options in order to customize their experience

### Supported Query Parameters

Appetize's App and Embed links can contain several additional query parameters to help customize your experience with your app.

You may see these query parameters in action when you run our [online demo](https://appetize.io/demo), and observe changes to your browser's address bar.

{% code title="Sample Structure" overflow="wrap" %}

```url
https://appetize.io/{app/embed}/{buildId|appId|publicKey}?{queryParameter1}={value1}&{queryParameter2}={value2}
e.g.
https://appetize.io/app/1234?device=pixel4&language=en
```

{% endcode %}

| Query Parameter                                                                     | Sample Values                                                                                                                                                                                                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <h4><strong>device</strong></h4>                                                    | iphone8, iphone11pro, iphone11promax, ipadair2, pixel4, pixel4xl, galaxytabs7                                                                                                                                            | <p>Specifies the device to simulate.<br><br>See <a href="/pages/OWXoELUBy5DnRtYZvi5K">Devices & OS Versions</a> for more values.</p>                                                                                                                                                                                                                                                                                                                                                      |
| <h4><strong>codec</strong></h4>                                                     | h264, jpeg                                                                                                                                                                                                               | Changes the codec used for video streaming.                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| <h4><strong>osVersion</strong></h4>                                                 | 11.4, 12.2, 13.3, 14.0                                                                                                                                                                                                   | <p>Specifies the operating system version on which to run the app.<br><br>See <a href="/pages/OWXoELUBy5DnRtYZvi5K">Devices & OS Versions</a> for more values.</p><p><strong>Note</strong>: It is recommended not to include <code>osVersion</code> as a query parameter when embedding the app, as this will always use our latest default version.</p>                                                                                                                                  |
| <h4><strong>scale</strong></h4>                                                     | a number between 10 and 100, or `auto`                                                                                                                                                                                   | Adjusts the size of the device shown on the page. `auto` will scale to fit the size of the iframe (embeds only).                                                                                                                                                                                                                                                                                                                                                                          |
| <h4><strong>autoplay</strong></h4>                                                  | true, false                                                                                                                                                                                                              | <p>When true, starts streaming the app on page load. Default is <code>false</code><br><br><strong>Note:</strong> If you are making use of our JavaScript SDK, we recommend starting the session programmatically using <code>client.startSession()</code> instead as this may cause the session to start before the SDK is ready.</p>                                                                                                                                                     |
| <h4><strong>orientation</strong></h4>                                               | portrait, landscape                                                                                                                                                                                                      | <p>Specifies the device orientation.<br>Default is <code>portrait</code></p>                                                                                                                                                                                                                                                                                                                                                                                                              |
| <h4><strong>centered</strong></h4>                                                  | vertical, horizontal, both                                                                                                                                                                                               | Centers the device (only when embedding).                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| <h4><strong>deviceColor</strong></h4>                                               | black, white                                                                                                                                                                                                             | Specifies the color of the device frame.                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| <h4><strong>screenOnly</strong></h4>                                                | true, false                                                                                                                                                                                                              | <p>When true, only shows the screen, i.e., no device frame.<br>Default is <code>false</code></p>                                                                                                                                                                                                                                                                                                                                                                                          |
| <h4><strong>toast</strong></h4>                                                     | top, bottom                                                                                                                                                                                                              | <p>Adjusts the position of Appetize toast messages used for displaying error or info messages.<br>Default is <code>bottom</code></p>                                                                                                                                                                                                                                                                                                                                                      |
| <h4><strong>xdocMsg</strong></h4>                                                   | true, false                                                                                                                                                                                                              | <p>When true, enables cross-document messages.<br><br><strong>Note:</strong> It is recommended to rather use our <a href="https://github.com/appetizeio/appetize-docs-gitbook/blob/master/features/broken-reference/README.md">Javascript SDK</a> to interact with the device.</p>                                                                                                                                                                                                        |
| <h4><strong>adbShellCommand</strong></h4><p><em>Android only</em></p>               | <p><code>am start -a android.intent.action.VIEW -d <https://appetize.io/></code><br>encoded as<br><code>am%20start%20-a%20android.intent.action.VIEW%20-d%20https%3A%2F%2Fappetize.io%2F</code><br></p>                  | Executes an `adb shell` command on the device.                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| <h4><strong>language</strong></h4>                                                  | af-ZA, fr-FR                                                                                                                                                                                                             | Specifies the language of the device via[ ISO 639-1 & BCP 47](https://stackoverflow.com/questions/7973023/what-is-the-list-of-supported-languages-locales-on-android) language codes.                                                                                                                                                                                                                                                                                                     |
| <h4><strong>locale</strong></h4><p><em>iOS Only</em></p>                            | en\_GB, fr\_FR                                                                                                                                                                                                           | Specifies the locale of the device via Locale ID.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| <h4><strong>iosKeyboard</strong></h4><p><em>iOS Only</em></p>                       | <p><code>lv\_LV\@sw=QWERTY</code><br>encoded as<br><code>lv\_LV%40sw%3DQWERTY%20</code></p>                                                                                                                              | <p>Specifies the iOS software keyboard to use.<br><a href="https://pgssoft.github.io/AutoMate/Enums/SoftwareKeyboard.html">Available Values</a></p>                                                                                                                                                                                                                                                                                                                                       |
| <h4><strong>iosAutocorrect</strong></h4><p><em>iOS Only</em></p>                    | true, false                                                                                                                                                                                                              | <p>Turn on Auto-Correction for iOS.</p><p>Defaults to <code>true</code></p>                                                                                                                                                                                                                                                                                                                                                                                                               |
| <h4><strong>disableVirtualKeyboard</strong></h4><p><em>Android Only</em></p>        | true, false                                                                                                                                                                                                              | <p>When true, disables the on-screen keyboard.<br>Default is <code>false</code></p>                                                                                                                                                                                                                                                                                                                                                                                                       |
| <h4><strong>location</strong></h4><p><em>iOS 12+</em><br><em>Android 10+</em></p>   | latitude, longitude, e.g. 39.903924,116.391432                                                                                                                                                                           | Specifies the simulated location of the device.                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| <h4><strong>timezone</strong></h4><p><em>Android Only</em></p>                      | `Australia/Adelaide` encoded as `Australia%2FAdelaide`                                                                                                                                                                   | <p>Specifies the URL-encoded timezone of the device.<br><a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Available Values</a></p>                                                                                                                                                                                                                                                                                                                                   |
| <h4><strong>grantPermissions</strong></h4>                                          | true, false                                                                                                                                                                                                              | <p>Automatically grant app permissions.<br>See <a href="/pages/n8zerXuzl3gGGgAYmWUz">Auto-grant Permissions</a>.</p>                                                                                                                                                                                                                                                                                                                                                                      |
| <h4><strong>hidePasswords</strong></h4><p><em>Android Only</em></p>                 | true, false                                                                                                                                                                                                              | Hides password visibility when typing.                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| <h4>launchApp</h4>                                                                  | `boolean \| string`                                                                                                                                                                                                      | <p>Indicates whether an app launches after installation and allows specifying which installed app to open.<br></p><p><strong>Possible values:</strong></p><ul><li><code>false</code> - Apps install but do not launch.</li><li><code>true</code> or <code>undefined</code> – Default behavior</li><li><code>appId</code> - Launches the app with the specified <a href="/pages/aQU2X92M0BDRXUO4LW5Z#app-identifier">app Identifier</a> (e.g., <code>com.android.chrome</code>).</li></ul> |
| <h4><strong>launchUrl</strong></h4>                                                 | `https://www.appetize.io` encoded as `https%3A%2F%2Fwww.appetize.io`                                                                                                                                                     | Specifies a deep link to open when the app is launched.                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| <h4><strong>launchArgs</strong></h4><p><em>iOS Only</em></p>                        | <p><code>\["apple", "banana", "pear"]</code></p><p>encoded as</p><p><code>%5B%22apple%22%2C%20%22banana%22%2C%20%22pear%22%5D</code></p>                                                                                 | Specifies a URL-encoded JSON array of strings to pass when launching the app.                                                                                                                                                                                                                                                                                                                                                                                                             |
| <h4><strong>debug</strong></h4>                                                     | true, false                                                                                                                                                                                                              | <p>When true, allows viewing the debug log for the app.<br>Default is <code>false</code></p>                                                                                                                                                                                                                                                                                                                                                                                              |
| <h4><strong>proxy</strong></h4>                                                     | <p><a href="http://example.com:8080/"><http://example.com:8080/></a></p><p>encoded as</p><p><code>http%3A%2F%2Fexample.com%3A8080%2F</code></p><p><br>For Appetize's intercepting proxy, use <code>intercept</code>.</p> | <p>Specifies a proxy server to route network traffic.</p><p><em><strong>Note</strong>:</em> Our current support is limited to HTTP Proxies. When your app makes HTTPS connections, the data remains encrypted despite the unencrypted connection to the proxy. The app sends a CONNECT request to the proxy for the destination HTTPS server, initiating an SSL handshake. The proxy acts as a TCP connection forwarder, ensuring end-to-end encryption for app data.</p>                 |
| <h4><strong>enableAdb</strong></h4><p><em>Android Only</em></p>                     | true, false                                                                                                                                                                                                              | <p>On session start, generates an SSH tunnel to allow ADB connections to the emulator.<br><br>For more information see <a href="/pages/VdqsJPfkS1P5ma7BcMqT">ADB tunnel</a>.</p>                                                                                                                                                                                                                                                                                                          |
| <h4><strong>record</strong></h4>                                                    | true, false                                                                                                                                                                                                              | Enables recording of all user actions that took place during the session. See [UI Automation](/features/ui-automation) for more information.                                                                                                                                                                                                                                                                                                                                              |
| <h4><strong>audio</strong></h4><p><em>Android Only</em></p>                         | true,false                                                                                                                                                                                                               | Enables audio output on the Appetize device.                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| <h4><strong>androidPackageManager</strong></h4><p><em>Android Only</em></p>         | true, false                                                                                                                                                                                                              | <p>Allows installation of additional APKs after app launch.<br>Default is <code>false</code></p>                                                                                                                                                                                                                                                                                                                                                                                          |
| <h4><strong>resetGms</strong></h4><p><em>Android Only</em></p>                      | true, false                                                                                                                                                                                                              | <p>Reset or reinitialize aspects of the Google Messaging Service.<br>Default is <code>false</code></p>                                                                                                                                                                                                                                                                                                                                                                                    |
| <h4><strong>region</strong></h4>                                                    | `us`,`eu`                                                                                                                                                                                                                | <p>Ensures that Appetize sessions are launched only from servers in a specific region.</p><p><strong>Note:</strong> It is best to avoid setting this property unless absolutely necessary. Our system automatically directs requests to the closest servers for optimal performance. Using this property could lead to longer queues in busy regions.</p>                                                                                                                                 |
| <h4><strong>appearance</strong></h4><p><em>iOS 13+</em><br><em>Android 10+</em></p> | light, dark                                                                                                                                                                                                              | <p>Applies the theme's appearance to the device.<br>Default is <code>light</code></p>                                                                                                                                                                                                                                                                                                                                                                                                     |
| <h4><strong>params</strong></h4>                                                    | `{"foo":"bar"}` encoded as `%7B%22foo%22%3A%22bar%22%7D`                                                                                                                                                                 | <p>A URL-encoded JSON object that will be passed to your app on launch. Use this to load custom content, skip onboarding, auto-login the specified user, or custom tracking.<br><br>More info:<br><a data-mention href="/pages/ni4Dtm7SMwzXKWgkZymF">/pages/ni4Dtm7SMwzXKWgkZymF</a></p>                                                                                                                                                                                                  |


# Features

Explore and Learn to Use Appetize’s Built-In Features: Switch Device Types, Monitor Network Traffic, Add Debug Logs, Automate User Actions, and More

## Learn more about

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Audio</td><td><a href="/files/N44NR4JrQAWkgtDbRW71">/files/N44NR4JrQAWkgtDbRW71</a></td><td></td><td></td><td><a href="/pages/DQ3FbZIMLZshCyNGUpeN">/pages/DQ3FbZIMLZshCyNGUpeN</a></td></tr><tr><td>Device &#x26; OS Versions</td><td><a href="/files/bU8AkdFczvX77xAoMoHP">/files/bU8AkdFczvX77xAoMoHP</a></td><td></td><td></td><td><a href="/pages/OWXoELUBy5DnRtYZvi5K">/pages/OWXoELUBy5DnRtYZvi5K</a></td></tr><tr><td>Network Traffic Monitor</td><td><a href="/files/rauZRUV3yH0fYNQ7Itzl">/files/rauZRUV3yH0fYNQ7Itzl</a></td><td></td><td></td><td><a href="/pages/Xczhqc5GWizFQCAkjYw8">/pages/Xczhqc5GWizFQCAkjYw8</a></td></tr><tr><td>Debug Logs</td><td><a href="/files/9Gb3Tude3dbYyjGcxHV5">/files/9Gb3Tude3dbYyjGcxHV5</a></td><td></td><td></td><td><a href="/pages/YLQLPDBpuPWGOQTrNSFr">/pages/YLQLPDBpuPWGOQTrNSFr</a></td></tr><tr><td>UI Automation</td><td><a href="/files/UOXVESPy0E1qXTb9hR5T">/files/UOXVESPy0E1qXTb9hR5T</a></td><td></td><td></td><td><a href="/pages/Gm30V6WlKjLSc4ey0eXF">/pages/Gm30V6WlKjLSc4ey0eXF</a></td></tr><tr><td>Proxy</td><td><a href="/files/h8LS1TBfAUvBzgD5KXVh">/files/h8LS1TBfAUvBzgD5KXVh</a></td><td></td><td></td><td><a href="/pages/ud2EDh3NNbIv1jY96jBD">/pages/ud2EDh3NNbIv1jY96jBD</a></td></tr><tr><td>Language &#x26; Locale</td><td><a href="/files/NcZ1qhxOf6QJCt22KX6x">/files/NcZ1qhxOf6QJCt22KX6x</a></td><td></td><td></td><td><a href="/pages/wtCz4JVjIMoNbUNqN1rH">/pages/wtCz4JVjIMoNbUNqN1rH</a></td></tr><tr><td>Mock Location</td><td><a href="/files/7ayL1JRb9H19gFLRoQxp">/files/7ayL1JRb9H19gFLRoQxp</a></td><td></td><td></td><td><a href="/pages/u5sdhDcOgIaqIN63Qx2w">/pages/u5sdhDcOgIaqIN63Qx2w</a></td></tr><tr><td>Deep Links</td><td><a href="/files/C3RzTjqVjNw8Eu5g7cx2">/files/C3RzTjqVjNw8Eu5g7cx2</a></td><td></td><td></td><td><a href="/pages/x9kYXf6iuSWg8hmtYhRM">/pages/x9kYXf6iuSWg8hmtYhRM</a></td></tr><tr><td>Launch Params</td><td><a href="/files/B3RuXUYP2Ycb9So10GUv">/files/B3RuXUYP2Ycb9So10GUv</a></td><td></td><td></td><td><a href="/pages/ni4Dtm7SMwzXKWgkZymF">/pages/ni4Dtm7SMwzXKWgkZymF</a></td></tr><tr><td>Auto-grant Permissions</td><td><a href="/files/S3pzBX9FlYVVAPcdL0WB">/files/S3pzBX9FlYVVAPcdL0WB</a></td><td></td><td></td><td><a href="/pages/n8zerXuzl3gGGgAYmWUz">/pages/n8zerXuzl3gGGgAYmWUz</a></td></tr><tr><td>Custom Branding</td><td><a href="/files/ljRM4cE68W0FrmasTwtJ">/files/ljRM4cE68W0FrmasTwtJ</a></td><td></td><td></td><td><a href="/pages/H1V2Zd5udamPSeIf9XbD">/pages/H1V2Zd5udamPSeIf9XbD</a></td></tr><tr><td>Custom Launch Pages</td><td><a href="/files/XIXfsUw5hYzTwLFOdQFk">/files/XIXfsUw5hYzTwLFOdQFk</a></td><td></td><td></td><td><a href="/pages/CSi72ZAWHbZTk4khJmQz">/pages/CSi72ZAWHbZTk4khJmQz</a></td></tr><tr><td>Advanced Features</td><td><a href="/files/IizJn4oFzavLQRKPyn4I">/files/IizJn4oFzavLQRKPyn4I</a></td><td></td><td></td><td><a href="/pages/l9QLPy5YvZNlkQS9kXP4">/pages/l9QLPy5YvZNlkQS9kXP4</a></td></tr></tbody></table>


# Audio

Stream the device's audio output to your browser. This lets you hear in-app sounds, media playback, and the TalkBack screen reader during a session.

{% hint style="info" %}
Audio is currently supported on **Android** devices only.
{% endhint %}

### Enabling audio

#### On the App Page

Add the [`audio`](/javascript-sdk/configuration#audio) query parameter to your app or embed URL:

```
https://appetize.io/app/<buildId|publicKey>?device=pixel7&audio=true
```

#### With the JavaScript SDK

Set `audio: true` in your session config:

```typescript
const client = await window.appetize.getClient("#appetize")

const session = await client.startSession({
    device: "pixel7",
    audio: true,
})
```

### Capturing audio frames

When audio is enabled, the session emits an `audio` event as frames arrive. The event includes the frame `buffer`, its `codec`, and the frame `duration`. Use it to record or mux audio alongside the `video` event:

```typescript
session.on("audio", ({ buffer, codec, duration }) => {
    // `buffer` contains the AAC audio frame
    // `codec` is "aac"
    // `duration` is the frame duration in seconds
})
```

### Live sample

Hear audio output combined with the TalkBack screen reader:

{% embed url="<https://samples.appetize.io/audio_talkback_experience/launch.html>" %}


# Devices & OS Versions

Effortlessly run your app on a diverse range of devices and operating systems

Appetize strive to support a healthy mixture of Android emulators and iOS simulators in order for you to get the most out of our product.

{% hint style="info" %}
We understand that every business has unique needs and that's why we also offer customized configurations and devices through our Private Cloud offerings. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

## Supported Devices

{% hint style="info" %}
If possible, we recommend indicating only the major version of an Operating System e.g. `17` instead of `17.2`, this way, if a newer version is released e.g. `17.3`, no updates is required to the app or embed pages.
{% endhint %}

| Device            | Device Identifier           | Operating System Versions    |
| ----------------- | --------------------------- | ---------------------------- |
| **iOS**           |                             |                              |
| iPhone 8          | iphone8                     | 15.5, 16.2                   |
| iPhone 8 Plus     | iphone8plus                 | 15.5, 16.2                   |
| iPhone 11 Pro     | iphone11pro                 | 15.5, 16.2, 17.2, 18.2, 26.0 |
| iPhone 12         | iphone12                    | 15.5, 16.2, 17.2, 18.2, 26.0 |
| iPhone 13 Pro     | iphone13pro                 | 15.5, 16.2, 17.2, 18.2       |
| iPhone 13 Pro Max | iphone13promax              | 15.5, 16.2, 17.2, 18.2, 26.0 |
| iPhone 14 Pro     | iphone14pro                 | 16.2, 17.2, 18.2, 26.0       |
| iPhone 14 Pro Max | iphone14promax              | 16.2, 17.2, 18.2, 26.0       |
| iPhone 15 Pro     | iphone15pro                 | 17.2, 18.2, 26.0             |
| iPhone 15 Pro Max | iphone15promax              | 17.2, 18.2, 26.0             |
| iPhone 16 Pro     | iphone16pro                 | 18.2, 26.0                   |
| iPhone 16 Pro Max | iphone16promax              | 18.2, 26.0                   |
| iPhone 17 Pro     | iphone17pro                 | 26.0                         |
| iPhone 17 Pro Max | iphone17promax              | 26.0                         |
| iPad Air          | ipadair4thgeneration        | 15.5, 16.2, 17.2, 18.2, 26.0 |
| iPad Pro 12.9"    | ipadpro129inch5thgeneration | 15.5, 16.2, 17.2, 18.2, 26.0 |
| iPad              | ipad9thgeneration           | 15.5, 16.2, 17.2, 18.2       |
| iPad Mini         | ipadmini6thgeneration       | 18.2, 26.0                   |
| **Android**       |                             |                              |
| Nexus 5           | nexus5                      | 8.1, 9.0, 10.0, 11.0         |
| Pixel 4           | pixel4                      | 10.0, 11.0, 12.0             |
| Pixel 4 XL        | pixel4xl                    | 10.0, 11.0, 12.0             |
| Pixel 6           | pixel6                      | 12.0, 13.0, 14.0, 15.0       |
| Pixel 6 Pro       | pixel6pro                   | 12.0, 13.0, 14.0, 15.0       |
| Pixel 7           | pixel7                      | 13.0, 14.0, 15.0             |
| Pixel 7 Pro       | pixel7pro                   | 13.0, 14.0, 15.0             |
| Pixel 8           | pixel8                      | 14.0, 15.0                   |
| Pixel 8 Pro       | pixel8pro                   | 14.0, 15.0                   |
| Pixel 9 Pro       | pixel9pro                   | 15.0, 16.0                   |
| Pixel 9 XL        | pixel9xl                    | 15.0, 16.0                   |
| Galaxy Tab S7     | galaxytabs7                 | 10.0, 11.0, 12.0, 13.0       |
| Pixel Tablet      | pixeltablet                 | 13.0, 14.0, 15.0, 16.0       |

{% hint style="info" %}
For devices utilizing a PIN/Lock Screen, our default PIN code is set to **1111** for seamless access and testing.
{% endhint %}

A dynamic, up-to-date list of all the devices and operating systems we support can be retrieved via our REST API

{% content-ref url="/pages/2iAtzrrK9P4S3X5YNmON" %}
[Devices & OS Versions](/rest-api/v1/devices-and-os-versions)
{% endcontent-ref %}

## Usage

{% hint style="warning" %}
Usage in our JavaScript SDK or query parameters require the device identifier e.g. `iphone8plus` or `ipadair4thgeneration`
{% endhint %}

### With Query Parameters

Set the device type and Operating System version by adding the `device` and/or `osVersion` query parameter to your app or embed URL.

```uri
&device=pixel4&osVersion=12.0
```

See [Query Params Reference](/platform/query-params-reference#device) for more information.

### With JavaScript SDK

Set the device type and Operating System version by adding the `device` and/or `osVersion` properties during configuration.

```typescript
await client.setConfig({
    device: 'pixel4',
    osVersion: '12.0'
})
```

See [Configuration](/javascript-sdk/configuration#device) for more information.


# Network Traffic Monitor

With Appetize, you can capture, inspect, validate and troubleshoot all network traffic (including any API calls) occurring during your app session for real-time or later analysis.

{% hint style="warning" %}
Note by default, a user needs to be authenticated to view network traffic using the built-in intercept proxy. See [App Permissions](/platform/app-management/app-permissions) for more information.
{% endhint %}

## Capture Network Traffic

To allow Appetize to capture all network events, you need to set the proxy of the device to `intercept`.

### With Query Parameter

Add the `proxy` query parameter to your app or embed URL with `intercept` as value.

```uri
&proxy=intercept
```

See [Query Params Reference](/platform/query-params-reference#proxy) for more information.

### With JavaScript SDK

Set `proxy` to `intercept` in the configuration e.g.

```typescript
await client.setConfig({
    proxy: "intercept",
    ...
})
```

See [Configuration](/javascript-sdk/configuration#proxy) for more information.

## Inspecting Network Traffic

### With App Page

The app page provides a simple network log of all the events that took place. You can access this via your app's app link

{% code overflow="wrap" %}

```url
https://appetize.io/app/{appId|buildId|publicKey}?&proxy=intercept
```

{% endcode %}

or by going to your [Apps](https://appetize.io/apps) page, selecting the app you want to inspect, and then clicking `debug` on the latest build or choosing a specific build you would like to debug.

<figure><img src="/files/aH0sPUouf1OLtnfG6rEn" alt=""><figcaption><p>Select <code>Debug</code> for the app or a specific build</p></figcaption></figure>

{% hint style="info" %}
You can also open the network logs in Chrome DevTools if you are running the app in a Chrome Browser by selecting the `Chrome DevTools` button.

Alternatively you can download the HAR file and open it in your favorite Network Monitoring tool.
{% endhint %}

<figure><img src="/files/Ayevx01YH9vcFE1MpBp5" alt="Example App Network Event Log"><figcaption><p>Network log tab with associated action buttons for downloading the HAR or opening in Chrome DevTools</p></figcaption></figure>

### With JavaScript SDK

You can listen for all network events via our JavaScript SDK. To easily view them in the browser you can print them to the console or you can store it to file for later analysis

```typescript
session.on('network', (data) => {
    // intercepted network request
    if (data.type === 'request') {
        console.log(`[${data.request.method}]: ${data.request.url}`)
    } 
    
    // intercepted network response
    if (data.type === 'response') {
        console.log(data.response.postData)
    }
})
```

See our JavaScript [API Reference](/javascript-sdk/api-reference#on-1) for more information.

{% hint style="info" %}
[getNetworkInspectorUrl](/javascript-sdk/api-reference#getnetworkinspectorurl) provides a direct URL for opening network logs in Chrome DevTools
{% endhint %}

## Troubleshooting

### Certificate Issues

When using Appetize’s **Network Intercept Proxy** to monitor **HTTPS traffic**, your app might encounter certificate errors if it uses certificate pinning. To resolve this, you can either:

1. **Remove the certificate pinning** from the app uploaded to Appetize, or
2. **Add Appetize's proxy's** SHA-256 hash to the list of trusted certificates in the app:

```
RNgAjJJXM4cdpieGhVEqa813muPE2imOWGQpFzwI63E=
```

By doing so, the app will trust the proxy’s certificate and allow secure HTTPS traffic monitoring.

{% hint style="warning" %}
If your app uses third-party libraries that also make HTTPS requests, those libraries might not be aware of the SHA-256 hash you add. In such cases, you may need to update the library’s certificate pinning or trust settings as well.
{% endhint %}

> **Note:**&#x20;


# Debug Logs

With Appetize, you can capture, inspect and troubleshoot all debug log events that occurred during your app session for real-time or later analysis.

{% hint style="warning" %}
Note by default, a user needs to be authenticated to view debug logs for their app. See [App Permissions](/platform/app-management/app-permissions) for more information.
{% endhint %}

## Capture Debug Logs

{% hint style="info" %}
Debug logs are generated by your app using [`NSLog`](https://developer.apple.com/documentation/foundation/1395275-nslog) or [`Logger`](https://developer.apple.com/documentation/os/logger) for iOS and [`Log`](https://developer.android.com/reference/android/util/Log) for Android
{% endhint %}

To enable Appetize to capture all debug log events, you can choose to enable it through a query parameter or with the help of the JavaScript SDK.

### With Query Parameter

Add the `debug=true` query parameter to your app or embed URL

```uri
&debug=true
```

See [Query Params Reference](/platform/query-params-reference#debug) for more information.

### With JavaScript SDK

Set `debug: true` in the configuration e.g.

```typescript
await client.setConfig({
    debug: true,
    ...
})
```

See [Configuration](/javascript-sdk/configuration#debug) for more information.

## Inspecting Debug Logs

### With App Page

The app page provides a simple debug log of all the events that took place. You can access this via your app's app link

{% code overflow="wrap" %}

```url
https://appetize.io/app/{appId|buildId|publicKey}?&debug=true
```

{% endcode %}

or by going to your [Apps](https://appetize.io/apps) page, selecting the app you want to inspect, and then clicking `debug` on the latest build or choosing a specific build you would like to debug.

<figure><img src="/files/aH0sPUouf1OLtnfG6rEn" alt=""><figcaption><p>Select <code>Debug</code> for the app or a specific build</p></figcaption></figure>

{% hint style="info" %}
Debug logs can also be downloaded via the `Download Logs` action at the top right of the log viewer. This is helpful when sharing logs with developers for troubleshooting purposes.
{% endhint %}

<figure><img src="/files/EyW1X6r1DjuDcYBT7BA5" alt="Example Debug logs from App Page"><figcaption></figcaption></figure>

### With JavaScript SDK

You can listen for all debug log events via our JavaScript SDK. To easily view them in the browser you can print them to the console or you can store it to file for later analysis

```typescript
// check debug logs
session.on('log', (data) => {
    console.log(data.message)
})
```

See our JavaScript [API Reference](/javascript-sdk/api-reference#on-1) for more information.


# Automations

Capture user interactions and play them back with ease using Appetize's Automation Recorder. Test and reuse app workflows (e.g. user login) on different devices effortlessly.

## Automation Recorder

Users can easily record and replay their interactions with our Appetize Devices using Appetize's Automation Recorder. These recordings capture the running application's user interface elements and are designed to handle minor app changes without any issues.

You can even record on one device, like an iPhone, and play it back on another device, such as an iPad.

{% hint style="info" %}
We welcome [customer feedback](mailto:hello@appetize.io) as we continue to refine our APIs, and the underlying technology powering our JavaScript SDK.
{% endhint %}

### Recording Actions

#### With App Page

The app page provides an easy way to enable **Automation Recorder** and logging out all of the user actions that took place. You can access this via your app's app link

```
https://appetize.io/app/{appId|buildId|publicKey}
```

or by going to your [Apps](https://appetize.io/apps) page and clicking `Start` on the app you want to inspect.

<figure><img src="/files/U1iZIvKZNUgYJfL3pWYk" alt=""><figcaption><p>Select <code>Start</code> on your app</p></figcaption></figure>

Once you are on your app page, you can enable **Automation Recorder** by toggling the button to on.

<figure><img src="/files/sEHEBSPG1vr7EF5efGEk" alt=""><figcaption><p>Enable Automation Recorder</p></figcaption></figure>

As you interact with the session, **Automation Recorder** will automatically pick up all the actions and log them out in the **Automation Recorder** tab.

<figure><img src="/files/hfiNB1J17uUYGfmGkMmT" alt=""><figcaption><p>All actions that took place during the session will be logged in the <strong>Automation Recorder</strong> tab.</p></figcaption></figure>

These actions can then be exported to either JSON for playback later or to a Playwright test file for testing purposes:

<figure><img src="/files/v0HeVZv7cNvKoasw48sE" alt=""><figcaption><p>Export <strong>Automation Recorder</strong> Actions for later playback or for testing.</p></figcaption></figure>

#### With JavaScript SDK

When the user interacts with the device, the session will emit an `action` event:

```javascript
const session = await client.startSession()

let actions = []
session.on('action', action => {
    actions.push(action)
})

// later on, replay them
await session.playActions(Actions)
```

Recorded actions can be serialized as `JSON` and stored so that you can replay them later.

{% code title="Example of an action" fullWidth="false" %}

```javascript
{
    type: 'click',
    xPos: 105,
    yPos: 645,    
    element: {
        text: 'Login'       
    }
}
```

{% endcode %}

### Playing Actions

#### With App Page

The app page makes it simple to turn on **AppRecorder** and import a JSON file with past user actions for replay. Just use your app's link to access and get started:

```
https://appetize.io/app/{appId|buildId|publicKey}
```

or by going to your [Apps](https://appetize.io/apps) page and clicking `Start` on the app you want to play the actions on.

<figure><img src="/files/U1iZIvKZNUgYJfL3pWYk" alt=""><figcaption><p>Select <code>Start</code> on your app</p></figcaption></figure>

Once you are on your app page, you can enable **Automation Recorder** by toggling the button to on.

<figure><img src="/files/sEHEBSPG1vr7EF5efGEk" alt=""><figcaption><p>Enable <strong>Automation Recorder</strong></p></figcaption></figure>

To import the JSON file with the user actions to replay, select the "Import JSON" button in the **Automation Recorder** tab and then select "Replay" to start a replay of the user actions that took place.

<figure><img src="/files/2zOh5w2fVCHuPTarkfdj" alt=""><figcaption><p>Import your JSON file and select Replay to replay the user actions.</p></figcaption></figure>

#### With JavaScript SDK

You can play an action on the device using `session.playAction`

```javascript
await session.playAction({
   type: 'click',
   element: {
      text: "submit",
      accessibilityIdentifier: "submit_button",
      class: "UIView"
   }
})
```

Multiple actions can be played back using `session.playActions`

```javascript
await session.playActions([
   {
      type: 'type',
      element: {
         accessibilityIdentifier: "email_field",
      }
   },
   {
      type: 'click',
      element: {
         text: "submit"
      }   
   }
])
```

{% hint style="info" %}
See [Touch Interactions](/javascript-sdk/automation/touch-interactions) for more information on how we match views and some best practices.
{% endhint %}

## Programmatic Interactions

The device can also be interacted with programmatically through our JavaScript API.

{% content-ref url="/pages/r3JFMcOkR53HGkAk9iWP" %}
[Device commands](/javascript-sdk/automation/device-commands)
{% endcontent-ref %}

{% content-ref url="/pages/SmU2Ki3FbZJAJGfHTlGZ" %}
[Touch interactions](/javascript-sdk/automation/touch-interactions)
{% endcontent-ref %}

## UI Testing with Appetize

We offer a [Playwright](https://playwright.dev/) integration that uses our JavaScript SDK to test your apps.

{% content-ref url="/pages/vmjne6Nf9pMTRH0PqkCO" %}
[Testing](/testing)
{% endcontent-ref %}


# Proxy

Take control of your network traffic with Appetize's advanced proxy support. Effortlessly reroute your traffic for better access control, privacy, and security.

{% hint style="info" %}
Our current support is limited to unauthenticated HTTP Proxies. When your app makes HTTPS connections, the data remains encrypted despite the unencrypted connection to the proxy. The app sends a CONNECT request to the proxy for the destination HTTPS server, initiating an SSL handshake. The proxy acts as a TCP connection forwarder, ensuring end-to-end encryption for app data.<br>

If you need to allow-list specific IPs for proxy access, you can use our [IP Blocks endpoint](/rest-api/v1/ip-blocks) to retrieve the necessary IP ranges.
{% endhint %}

## App Level Proxy

Appetize supports settings a proxy server on a per-app basis.

{% hint style="warning" %}
App-Level proxy settings will override the Account **`default proxy`**. if you *always* want the Account-level proxy to be used, use **`forced proxy`** instead.
{% endhint %}

To allow Appetize to proxy all the network events, you need to specify a proxy server to route network traffic to:

### With Query Parameter

Add the `proxy` query parameter to your app or embed URL with your URL encoded proxy server's address (e.g. `http://example.com:8080/`)as value.

```uri
&proxy=http%3A%2F%2Fexample.com%3A8080%2F
```

See [Query Params Reference](/platform/query-params-reference#proxy) for more information.

### With JavaScript SDK

Set `proxy` to `http://example.com:8080/` in the configuration e.g.

```typescript
await client.setConfig({
    proxy: "http://example.com:8080",
    ...
})
```

See [Configuration](/javascript-sdk/configuration#proxy) for more information.

## Organization Level Proxy

{% hint style="info" %}
Organization Level Proxy is only available on our Premium and Enterprise plans. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

You may also set organization-wide proxy settings by navigating to [**Organization** **->** **Proxy settings**](https://appetize.io/organization/proxy-settings).

### Default Proxy

By setting a default proxy, the proxy will be used when no other proxy is specified.

<figure><img src="/files/1nfzPUq31hPmjkvqcHLk" alt=""><figcaption><p>Example default proxy</p></figcaption></figure>

### Forced Proxy

By setting a forced proxy, the proxy will be used regardless of other proxy settings.

<figure><img src="/files/OMI1uyDrI8lnvRoCCNdG" alt=""><figcaption><p>Example forced proxy</p></figcaption></figure>


# Language & Locale

Appetize supports multiple languages and locales for running your mobile apps in different regions and languages.

## Language

{% hint style="warning" %}
Note that for `iOS` we currently only set the language at the app level. Our Private Cloud offerings allow for setting system level language configurations. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

### With Query Parameter

Set the language of the device by adding the `language` query parameter to your app or embed URL.

```uri
&language=af-ZA
```

See [Query Params Reference](/platform/query-params-reference#language) for more information.

### With JavaScript SDK

Set the language of the device via our JavaScript SDK

#### With Configuration

```typescript
await client.setConfig({
    language: 'af-ZA',
    ...
})
```

See [Configuration](/javascript-sdk/configuration#language) for more information.

#### With `SetLanguage`

```typescript
await session.setLanguage("af-ZA")
```

See [API Reference](/javascript-sdk/api-reference#setlanguage) for more information

## Locale

*iOS Only*

### **With Query Parameter**

Set the locale of the device by adding the `locale` query parameter to your app or embed URL.

```
&locale=fr_FR
```

See [Query Params Reference](/platform/query-params-reference#locale) for more information

### **With JavaScript SDK Configuration**

Set the locale of the device via our JavaScript SDK configuration

```typescript
await client.setConfig({
    locale: 'fr_FR',
    ...
})
```

See [Configuration](/javascript-sdk/configuration#locale) for more information

## Timezone

*Android Only*

### **With Query Parameter**

Set the time zone of the device by adding the `timezone` query parameter to your app or embed URL.

```
&timezone=Australia%2FAdelaide
```

See [Query Params Reference](/platform/query-params-reference#timezone) for more information

### **With JavaScript SDK Configuration**

Set the time zone of the device via our JavaScript SDK configuration

```typescript
await client.setConfig({
    timezone: 'Australia/Adelaide',
    ...
})
```

See [Configuration](/javascript-sdk/configuration#timezone) for more information

## iOSKeyboard

*iOS Only*

We also support an `iosKeyboard` [Query Parameter](#with-query-parameter-3) and [JavaScript SDK Configuration](#with-javascript-sdk-configuration-2) to specify the exact keyboard for iOS. The Android keyboard does not need this feature, as it updates automatically based on the language specified. A full list of supported keyboards can be found [here](https://pgssoft.github.io/AutoMate/Enums/SoftwareKeyboard.html).

### **With Query Parameter**

Set the keyboard of the device by adding the `iosKeyboard` query parameter to your app or embed URL.

```
&iosKeyboard=ja_JP@sw
```

See [Query Params Reference](/platform/query-params-reference#ioskeyboard) for more information

### **With JavaScript SDK Configuration**

Set the keyboard of the device by adding the `iosKeyboard` field via our JavaScript SDK configuration

```typescript
await client.setConfig({
    iosKeyboard: 'ja_JP@sw',
    ...
})
```

See [Configuration](/javascript-sdk/configuration#ioskeyboard) for more information.

## Sample Usage

#### Japanese language and keyboard

<https://appetize.io/demo?language=ja&iosKeyboard=ja_JP@sw>

#### French language with French AZERTY keyboard

<https://appetize.io/demo?language=fr&iosKeyboard=fr_FR@sw>


# Mock Location

Appetize supports simulating device location for running and testing location-based applications easily.

### With Query Parameter

To specify the device location, include the `location` query parameter in your app or embed URL, followed by the latitude and longitude values e.g.

```uri
&location=-33.924434,18.418391
```

See [Query Params Reference](/platform/query-params-reference#location) for more information.

### With JavaScript SDK

Set the location of the device via our JavaScript SDK

#### With Configuration

Specify the location by passing in a number array in format `[latitude, longitude]` e.g.

```typescript
await client.setConfig({
    location: [-33.924434, 18.418391],
    ...
})
```

See [Configuration](/javascript-sdk/configuration#location) for more information.

#### With setLocation()

Specify the location by passing in the number parameters `latitude` and `longitude` e.g.

```typescript
await setLocation(-33.924434, 18.418391)
```

See the [API Reference](/javascript-sdk/api-reference#setlocation) for more information.


# Deep links

Appetizes deep linking feature can be used to simplify user workflows and reduce friction by allowing users to jump directly to relevant content or actions.

{% hint style="success" %}
Supported Links

* Android AppLinks / iOS Universal Links
* Custom Schema Deeplinks
* Web Links
  {% endhint %}

Appetize allows configuration of deep links at app launch with[ launchUrl](#launchurl) or during runtime with [openUrl](#openurl), depending on when and how the link needs to be triggered.

## launchUrl

To launch a URL (deep link or regular) when the device starts.

{% hint style="info" %}
Verifies AppLink or Universal Link associations (if applicable), which may delay the device launch until the validation is complete
{% endhint %}

{% tabs %}
{% tab title="Query Parameter" %}
Set the deep-link URL by adding the URL encoded `launchUrl` query parameter to your app or embed URL.

```url
&launchUrl=https%3A%2F%2Fwww.appetize.io
```

See [Query Params Reference](/platform/query-params-reference#launchurl) for more information.
{% endtab %}

{% tab title="JavaScript SDK" %}
Set the deep-link URL of the device via our JavaScript SDK

```typescript
await client.setConfig({
    launchUrl: "https://www.appetize.io",
    ...
})
```

See [Configuration](/javascript-sdk/configuration#launchurl) for more information.
{% endtab %}
{% endtabs %}

## openUrl

To launch a URL (deep link or regular) while the device (or application) is already running. On iOS, URLs passed with `launchUrl` or `openUrl` are limited to 2048 characters.

{% hint style="info" %}
This can be called multiple times after launch of the device, with the assumption that all AppLink and Universal Link associations are already established.
{% endhint %}

```typescript
await session.openUrl("https://appetize.io")
```

See the [API Reference](/javascript-sdk/api-reference#openurl) for more information.

## Troubleshooting

{% hint style="warning" %}
Please note that the use of AppLinks and Universal Links may be affected if our network traffic monitor feature is enabled.
{% endhint %}

### Verifying Associated Domains Entitlement included in your iOS App

1. Open the Terminal on your macOS machine.
2. Navigate to the directory where your app's `.app` bundle is located. For example, if your app is named `YourAppName`, and it's in the `/Applications` folder, you can use the following command to change to that directory:

```bash
cd /Applications/YourAppName.app
```

3. Run the `codesign` command with the `--entitlements` flag to extract the entitlements XML from your app bundle:

<pre class="language-bash"><code class="lang-bash"><strong>codesign -d --entitlements - YourAppName.app/
</strong></code></pre>

This command will print the entitlements XML to the Terminal.

4. Verify that the entitlements XML contains the `com.apple.developer.associated-domains` key and that it specifies the expected URL for your associated domain. The output should look like the following:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.developer.associated-domains</key>
    <array>
      <string>applinks:yourexpected.domain.com</string>
    </array>
  </dict>
</plist>
```

Ensure that the `<string>` value within `<array>` corresponds to the domain you expect for your app's associated domains.


# Launch Params

With Launch Params, you can pass custom data to your mobile apps while running in Appetize. It can be useful to load custom content, skip onboarding, auto-login a specified user, or custom tracking.

## Passing Data to your Application

### With Query Parameter

Set the params data to pass to your application. The data needs to be an URL-Encoded JSON Object e.g.

```json
&params={"foo":"bar"}
```

should be encoded to

```url
&params=%7B%22foo%22%3A%22bar%22%7D
```

See [Query Params Reference](/platform/query-params-reference#params) for more information.

### With JavaScript SDK

Send the params data to pass to your application as part of the configuration. The data needs to be a JSON Object e.g.

```typescript
await client.setConfig({
    params: {"foo":"bar"},
    ...
})
```

See [Configuration](/javascript-sdk/configuration#params) for more information.

## Retrieving Data in your Application

{% hint style="info" %}
For convenience, we set the key **`"isAppetize": true`** while streaming your app to allow you to easily detect if your app is running in Appetize.
{% endhint %}

{% tabs %}
{% tab title="Android (Java)" %}
**With Intents**

The data will be passed as extras into the intent that launches your app, accessible by calling the appropriate get method (based on type) e.g.

```java
Intent intent = getIntent()
intent.getBooleanExtra("isAppetize", false);
intent.getStringExtra("stringKey");
...
```

**With SharedPreferences**

The data will also be stored in SharedPreferences under a file called `prefs.db`. This is accessibly by fetching that `SharedPreferences` instance and calling the appropriate get method e.g.

{% code overflow="wrap" %}

```java
SharedPreferences preferences = getApplicationContext().getSharedPreferences("prefs.db", Context.MODE_PRIVATE);
preferences.getBoolean("isAppetize", false);
preferences.getString("stringKey", null);
...
```

{% endcode %}

{% hint style="warning" %}
Complex types (e.g. arrays or objects) will automatically be serialized and need to be deserialized manually before using e.g. passing an object:

```json
{
  "obj": { "stringKey": "value", "boolKey": true }
}
```

when queried, will return:

```
"{"stringKey":"value","boolKey":true}"
```

{% endhint %}
{% endtab %}

{% tab title="Android (Kotlin)" %}
**With Intents**

The data will be passed as extras into the intent that launches your app, accessible by calling the appropriate get method (based on type) e.g.

```kotlin
intent.getBooleanExtra("isAppetize", false)
intent.getStringExtra("stringKey")
...
```

**With SharedPreferences**

The data will also be stored in SharedPreferences under a file called `prefs.db`. This is accessibly by fetching that `SharedPreferences` instance and calling the appropriate get method e.g.

{% code overflow="wrap" %}

```kotlin
val preferences = applicationContext.getSharedPreferences("prefs.db", Context.MODE_PRIVATE);
preferences.getBoolean("isAppetize", false)
preferences.getString("stringKey", null)
...
```

{% endcode %}

{% hint style="warning" %}
Complex types (e.g. arrays or objects) will automatically be serialized and need to be deserialized manually before using e.g. passing an object:

```kotlin
{
  "obj": { "stringKey": "value", "boolKey": true }
}
```

when queried, will return:

```
"{"stringKey":"value","boolKey":true}"
```

{% endhint %}
{% endtab %}

{% tab title="iOS (ObjC)" %}
The data passed will be stored in the shared defaults object, accessible by calling the appropriate method (based on type) e.g.

```objectivec
[[NSUserDefaults standardUserDefaults] boolForKey:@"isAppetize"]
[[NSUserDefaults standardUserDefaults] objectForKey:@"objectKey"]
[[NSUserDefaults standardUserDefaults] stringForKey:@"stringKey"]
...
```

{% hint style="warning" %}
Note that extension bundles will not have access to the app's standard `UserDefaults`. To work around this issue, please see [Sharing Data with Your Containing App](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1).
{% endhint %}
{% endtab %}

{% tab title="iOS (Swift)" %}
The data passed will be stored in the shared defaults object, accessible by calling the appropriate method (based on type) e.g.

```swift
UserDefaults.standard.bool(forKey: "isAppetize")
UserDefaults.standard.object(forKey: "objectKey")
UserDefaults.standard.string(forKey: "stringKey")
...
```

{% hint style="warning" %}
Note that extension bundles will not have access to the app's standard `UserDefaults`. To work around this issue, please see [Sharing Data with Your Containing App](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1).
{% endhint %}
{% endtab %}
{% endtabs %}


# Media

Easily upload images and other media to your Appetize iOS and Android devices, programmatically or via the App Page.

{% hint style="info" %}
The maximum file size for uploading media is **50 MB**.
{% endhint %}

## Supported Files

| Platform | Supported File Types     |
| -------- | ------------------------ |
| Android  | All file types           |
| iOS      | PNG, JPEG, JPG, GIF, MP4 |

{% hint style="warning" %}
Files outside of these formats will not be processed on iOS simulators.
{% endhint %}

## With App Page

The [App Page](/platform/app-management/running-apps#accessing-your-app-page) provides an **Upload File** button on the device toolbar during active sessions, allowing you to upload media files directly to the device’s photo library.

<figure><img src="/files/UfnLEdUMxAV3mFPQpUkT" alt=""><figcaption><p>"Upload File" button during active sessions</p></figcaption></figure>

## With JavaScript SDK

Appetize’s [JavaScript SDK](/javascript-sdk) offers a convenient [addMedia](/javascript-sdk/api-reference#addmedia-file) function for adding media files directly to your active simulator sessions. This function is ideal for automating media uploads as part of your testing or development workflows.

```typescript
await session.addMedia(file)
```


# Auto-grant Permissions

Automatically grants all required app runtime permissions to provide users with a seamless experience.

## Enable Auto-grant Permissions

Eliminate the hassle of manual permission grants. This feature ensures app [runtime permissions](https://source.android.com/docs/core/permissions/runtime_perms) are automatically handled for each session. Examples of runtime permissions include location, external storage, microphone, camera, and more. Simplify your users' journey.

<figure><img src="/files/m8QEOLpgX5vKw0lulVaf" alt="" width="188"><figcaption><p>Example runtime permission on Android</p></figcaption></figure>

### Supported permissions

{% tabs %}
{% tab title="Android" %}

| Permission    | Supported |
| ------------- | :-------: |
| Bluetooth     |     ✅     |
| Calendar      |     ✅     |
| Camera        |     ✅     |
| Contacts      |     ✅     |
| Location      |     ✅     |
| Media Library |     ✅     |
| Microphone    |     ✅     |
| Notifications |     ✅     |
| Phone         |     ✅     |
| Storage       |     ✅     |
| SMS           |     ✅     |
| {% endtab %}  |           |

{% tab title="iOS" %}

| Permission    | Supported |
| ------------- | :-------: |
| Bluetooth     |     ❌     |
| Calendar      |     ✅     |
| Camera        |     ✅     |
| Contacts      |     ✅     |
| Health        |     ❌     |
| HomeKit       |     ✅     |
| Location      |     ✅     |
| Media Library |     ✅     |
| Microphone    |     ✅     |
| Motion        |     ✅     |
| Notifications |     ❌     |
| Deep Links    |     ✅     |
| Photos        |     ✅     |
| Reminders     |     ✅     |
| Siri          |     ✅     |
| Speech        |     ❌     |
| UserTracking  |     ✅     |
| {% endtab %}  |           |
| {% endtabs %} |           |

### With Query Parameter

Add the `grantPermissions=true` query parameter to your app or embed URL.

```uri
&grantPermissions=true
```

See [Query Params Reference](/platform/query-params-reference#grantpermissions) for more information.

### With JavaScript SDK

Set `grantPermissions: true` in the configuration e.g.

```typescript
await client.setConfig({
    grantPermissions: true,
    ...
})
```

See [Configuration](/javascript-sdk/configuration#grantpermissions) for more information.


# Custom Branding

Create a seamless brand experience with Appetize's Custom Branding. Add a personalized touch to app links with a custom domain, loading animations, Pre-Launch and Post-Session Graphics and more!

{% hint style="info" %}
Custom Branding is only available on our Enterprise plans. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

Appetize offers a fully White Label solution. This includes:

* [Custom Domain](#custom-domain)
* Removal of all Appetize Branding
* [Pre-Launch & Post-Session Graphics](#pre-launch-and-post-session-graphics)
* Custom Device Chrome

## Custom Domain

<figure><img src="/files/flRqOInicdJmoOyUOYxv" alt=""><figcaption><p>Example Custom Domain</p></figcaption></figure>

If you have a custom domain, the app links you share and embed will display your domain name instead of `appetize.io`. For example, if your domain is `preview.example.com`, that's how the app and embed links will appear.

To enable this feature, you'll need to set up a CNAME record that points your desired domain or subdomain to `whitelabel.appetize.io`.

Once setup, please [contact us](mailto:support@appetize.io) to finish the configuration.

{% hint style="info" %}
Appetize uses industry standard [Let's Encrypt](https://letsencrypt.org/) for SSL/TLS configuration of your domain.
{% endhint %}

## Pre-Launch & Post-Session Graphics

From your Appetize [Dashboard](https://appetize.io/apps), select the app and then select the build you want to apply the graphics on

<figure><img src="/files/KXZ5kA5aIr3qNB2vmhOM" alt=""><figcaption><p>Select the App you want to modify</p></figcaption></figure>

<figure><img src="/files/AhLEyqhcPAucLjaCtTEx" alt=""><figcaption><p>Select the build to modify</p></figcaption></figure>

From there, you will see options to customize the Pre-Launch and Post-Session Graphics under the settings tab.

<figure><img src="/files/Ejo26U1HbmPjynT0WgmG" alt=""><figcaption><p>Example Pre-Launch Styling</p></figcaption></figure>

<figure><img src="/files/d0ZNBPTaNukhaBO6t1Kv" alt=""><figcaption><p>Example Post-Session Styling</p></figcaption></figure>


# Custom Launch Pages

Appetize supports templated "Launch Pages" that can provide a simple bookmark-friendly page for your team.

{% hint style="info" %}
Custom Launch Pages is only available on our Enterprise plans. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

<figure><img src="/files/9rJgcovML74XeVSL7ngs" alt=""><figcaption></figcaption></figure>

Customers can create highly customized integrations using Appetize virtual devices, thanks to our [embedding](/platform/embedding-apps), [Query Parameter](/platform/query-params-reference) support, and [JavaScript SDK](/javascript-sdk).

For convenience, we also provide templated "launch pages" that can provide a simple bookmark-friendly page for your team. For Enterprise customers, Appetize can host this page and display it in the side menu for easy access. We are happy to assist you in designing and implementing such as page for your specific business use.

### Sample Pages

You can find examples of Launch Pages at

{% embed url="<https://samples.appetize.io>" %}

Each example includes a working demo and a link to the source code, which you can use as a starting point for your own page. Feel free to copy, modify, or adapt for your own needs in any way you'd like.


# Advanced Features


# Android


# ADB tunnel

The ADB tunnel feature allows you to create an SSH tunnel to a running Appetize Android session, enabling you to interact with the device via Android Studio or standard ADB protocol.

## Enable ADB Tunnel

To enable the ADB tunnel feature, you can either choose to enable it through a query parameter or by utilizing the JavaScript SDK.

### With Query Parameter

Add the `enableAdb=true` query parameter to your app or embed URL

```uri
&enableAdb=true
```

See [Query Params Reference](/platform/query-params-reference#enableadb) for more information.

### With JavaScript SDK

Set `enableAdb: true` in the configuration e.g.

```typescript
await client.setConfig({
    enableAdb: true,
    ...
})
```

See [Configuration](/javascript-sdk/configuration#enableadb) for more information.

## Usage

### With App Page

{% hint style="info" %}
One common practice is to use the ADB tunnel to connect to an Android "standalone" device, without any specific app installed. For more information see [Standalone Device](/platform/standalone-device).
{% endhint %}

The app page provides a simple way to retrieve the `adb` information required to connect to the device.

You can access this via your app's app link

{% code overflow="wrap" %}

```url
https://appetize.io/app/{appId|buildId|publicKey}?&enableAdb=true
```

{% endcode %}

or by going to your [Apps](https://appetize.io/apps) page, selecting `Play` on the app you want to inspect, and then toggling `Adb Tunnel` to `On`

<figure><img src="/files/9awQumwzM15K5k2zONS9" alt=""><figcaption><p>Select <code>Play</code> on the app you want to inspect</p></figcaption></figure>

<figure><img src="/files/B2Kv1rqT9fUFCYf6qBCQ" alt="Example ADB Tunnel Action Switched to On"><figcaption><p>Toggle ADB tunnel to "On"</p></figcaption></figure>

Select `Tap To Start` (or your equivalent text to start the session). A command will then be generated that you need to copy and paste in your shell environment e.g.

<figure><img src="/files/Qex9o5Ui3ODUIJ5mpvCO" alt="Example command to paste in shell environment"><figcaption></figcaption></figure>

Then, the Appetize virtual Android device will appear with `adb devices`, as if it were a device plugged into your computer via USB.

### With JavaScript SDK

You can retrieve all the information needed to start an ADB session via the `adbConnection` property

```typescript
const adbInfo = session.adbConnection
const command = adbInfo.command
```

See our JavaScript [API Reference](/javascript-sdk/api-reference#adbconnection) for more information.


# Hide Password Visibility

Keep your passwords secure on Android with Appetize's Hide Password Visibility feature that ensures your passwords are hidden from view.

### With Query Parameter

Add the `hidePasswords=true` query parameter to your app or embed URL

```uri
&hidePasswords=true
```

See [Query Params Reference](/platform/query-params-reference#hidepasswords) for more information.

### With JavaScript SDK

Set `hidePasswords: true` in the configuration e.g.

```typescript
await client.setConfig({
    hidePasswords: true,
    ...
})
```

See [Configuration](/javascript-sdk/configuration#hidepasswords) for more information.


# Reserved Devices

Ensure lightning-fast loading times for commonly used apps by keeping them ready to go on reserved devices.

{% hint style="info" %}
This feature is only available on our Premium and Enterprise plans. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

With reserved devices, Appetize will pre-install your specified apps onto a set of virtual devices. These devices are set aside for solely your use.

When an incoming session is requested for your app, the session can be served from one of these reserved devices, and loads nearly instantaneously for you user. The user does not need to wait for the download and installation of your app, or in rare cases needing to switch the device if the requested device is unavailable. Loading time is extremely fast.

### Requesting Reserved Devices

To request a reserved device through Appetize, you need to provide specific information.

* **buildId:** Application build identifier (previously known as publicKey).
* **deviceType:** The device that is going to be reserved. e.g., iphone14pro.
* **OSVersion:** The operating system version for the device. e.g., 17.2.

See [Devices & OS Versions](/features/devices-and-os-versions) for possible device combinations.

### Accessing your Reserved Device

Once your reserved device has been configured and is ready for use, you can access it through the following URL:

Replace the **buildId**, **deviceType**, and **OSVersion** placeholders with the values provided.

{% code overflow="wrap" %}

```
https://appetize.io/app/{buildId}?device={deviceType}&osVersion={OSVersion}
```

{% endcode %}


# Account

Manage Your Team, Configure Single Sign-On, and Access Reporting Features

## Learn more about

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td>Invite your team</td><td></td><td></td><td><a href="/pages/xQ9eT4ImJwhjiw2lXnzQ">/pages/xQ9eT4ImJwhjiw2lXnzQ</a></td><td><a href="/files/K5oPGMi18gwPJIn3IovS">/files/K5oPGMi18gwPJIn3IovS</a></td></tr><tr><td>Single Sign-On</td><td></td><td></td><td><a href="/pages/pgnI0lqah0JIDCVYOOEX">/pages/pgnI0lqah0JIDCVYOOEX</a></td><td><a href="/files/C9cUsuaNcWuRbrzdRukI">/files/C9cUsuaNcWuRbrzdRukI</a></td></tr><tr><td>API Token Management</td><td></td><td></td><td><a href="/pages/YFzS8z7XNRZPGWGS2zaJ">/pages/YFzS8z7XNRZPGWGS2zaJ</a></td><td><a href="/files/uVlwmTJQQbfdGxK1V9oZ">/files/uVlwmTJQQbfdGxK1V9oZ</a></td></tr><tr><td>Reporting</td><td></td><td></td><td><a href="/pages/JRS6S9sRT4yugWNh8Rx9">/pages/JRS6S9sRT4yugWNh8Rx9</a></td><td><a href="/files/Y3mEt4tZ0Tf4b8Nl3lmL">/files/Y3mEt4tZ0Tf4b8Nl3lmL</a></td></tr><tr><td>Session History</td><td></td><td></td><td><a href="/pages/Tz5YSCR7ryFrozwZjm1E">/pages/Tz5YSCR7ryFrozwZjm1E</a></td><td><a href="/files/01ZRwWsCSwxm21HALHbO">/files/01ZRwWsCSwxm21HALHbO</a></td></tr></tbody></table>


# Invite your team

Invite, add, or remove team members on Appetize with ease! Utilize regular usernames and passwords or Single-Sign-On (SSO) to streamline the process.

## Username and Password

Appetize supports regular  username and password logins by *default*. As an admin user, you can invite users and manage roles directly from the [**Organization → Team Management**](https://appetize.io/organization/team) page.

### Inviting Users

1. Navigate to the [**Team Management**](https://appetize.io/organization/team) screen.

2. In the **Invite new members** section, you can:
   * Enter **multiple email addresses** in one field to assign them all the same role.
   * Or click **+ Add More** to add separate email groups with different roles.<br>

     <figure><img src="/files/tBYmEHe2VFs0j1YgUSNg" alt=""><figcaption><p>Invite new members section</p></figcaption></figure>

3. Use the dropdown next to each email group to assign a role:
   * **Viewer** – Can only run uploaded apps.
   * **Developer** – Upload apps and manage app settings.
   * **Admin** – View account page, edit billing, manage users, and change account settings.<br>

     <figure><img src="/files/b37kQ4tARYy0UnrQlpUu" alt=""><figcaption><p>User Roles Drop-down</p></figcaption></figure>

4. Click **Send Invites** to send all invitations at once.

{% hint style="info" %}
Once a user is added to your account, all their existing and future uploaded apps will be linked to this account.
{% endhint %}

### Managing Roles and Team Members

After users accept their invitations, you can update their roles or remove them from the team:

1. Scroll to the **Existing Members** section.
2. Use the dropdown next to each user’s name to change their role.
3. To remove a user, click the **⊖** icon next to their row.

| User Role | Permissions                                                                |
| --------- | -------------------------------------------------------------------------- |
| Admin     | View account page, edit billing, manage users, and change account settings |
| Developer | Upload apps and manage app settings                                        |
| Viewer    | Can only run uploaded apps                                                 |

<figure><img src="/files/W6gJXFGfG9LVHVvNjc6t" alt=""><figcaption><p>Adjust user roles and remove users from your Team Management page</p></figcaption></figure>

## Single Sign-On (SSO)

On our Enterprise plans, you can take advantage of our Single Sign-On (SSO) integrations for seamless user management on Appetize. Visit our [documentation](/account/single-sign-on) for more details.

{% hint style="info" %}
Roles for SSO users are managed through your identity provider. You do not need to assign or update them manually in Appetize.
{% endhint %}

## Related Links

{% content-ref url="/pages/wxvx657b3flrbJMnrVMW" %}
[App Permissions](/platform/app-management/app-permissions)
{% endcontent-ref %}


# Single Sign-On

{% hint style="info" %}
Single Sign-on is only available on our Enterprise plans. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

Appetize supports SSO integrations using several different providers.

We have provided instructions below. We are also happy to setup and test the SSO integration over a phone call. In that case, please [contact us](mailto:hello@appetize.io).

{% hint style="warning" %}
Once SSO is enabled for your account, all user access and role assignments are managed entirely by your SSO provider.
{% endhint %}

### Role assignments

Please create the following groups within your SSO provider. Assigning users to each group is equivalent to granting them the corresponding role within your Appetize account.

{% hint style="info" %}
Appetize supports role names that matches the following regex:

`/(^|[_-])appetize[_-](admin|developer|user)?$/i`
{% endhint %}

| Role                | Permissions                                                               |
| ------------------- | ------------------------------------------------------------------------- |
| appetize\_user      | Can only run uploaded apps.                                               |
| appetize\_developer | Can upload apps, delete apps, manage app settings.                        |
| appetize\_admin     | Can manage account settings, download usage reports, download audit logs. |

### Providers

{% content-ref url="/pages/rbq2pK8euZBQOcJh2Wdd" %}
[OpenID Connect](/account/single-sign-on/openid-connect)
{% endcontent-ref %}

{% content-ref url="/pages/3lL0B8KFkOeshqpkRKw0" %}
[SAML](/account/single-sign-on/saml)
{% endcontent-ref %}

{% content-ref url="/pages/I9iNxJEigdwl5RYbz8My" %}
[Azure Active Directory](/account/single-sign-on/azure-active-directory)
{% endcontent-ref %}

{% content-ref url="/pages/dri3k8hoNjR42StCEfbb" %}
[Google Workspace (GSuite)](/account/single-sign-on/google-workspace-gsuite)
{% endcontent-ref %}


# OpenID Connect

{% hint style="info" %}
*Every SSO provider is a little bit different. Please* [*contact us*](mailto:hello@appetize.io) *with any questions!*
{% endhint %}

## Check authorization server groups scope

Check the "scopes" configuration of your authorization server, and verify there is a scope called `groups`. If not, add a scope named `groups`.

<figure><img src="/files/242uYTMcTqwKGzi5NR7X" alt=""><figcaption><p>OKTA add groups scope example. In Security -> API -> Authorization servers -> Choose Server -> Scopes</p></figcaption></figure>

## Create a new application

<figure><img src="/files/MffjjI9yPWxN4Xgv9Gal" alt="Example creating new &#x22;Web&#x22; application in OKTA"><figcaption><p>Example creating new "Web" application in OKTA</p></figcaption></figure>

## Configure app settings

| Field               | Value                         |
| ------------------- | ----------------------------- |
| Allowed grant types | Authorization Code            |
| Login redirect URIs | TBD - provided by Appetize.io |
| Initiate login URI  | TBD - provided by Appetize.io |

<figure><img src="/files/lNGeJbKugiBcjngjk2XC" alt="Example app settings in OKTA"><figcaption><p>Example app settings in OKTA</p></figcaption></figure>

### Add group assignments to claims

We will need to configure your SSO provider to send over the user's groups assignments after a successful login.

The following example shows how to pass through groups with prefix appetize\_\* as a groups claim within OKTA. This can be done by adding the groups claim to your authorization server at API -> Authorization Servers. For some OKTA clients, this can also be done under the "Sign On" section in your app's configuration, where you can add groups the same way.

<figure><img src="/files/K6XGDamteqz21sCGnWLK" alt="Example including appetize_* group assignments claim in OKTA"><figcaption><p>Example including appetize_* group assignments claim in OKTA</p></figcaption></figure>

## **Information to provide to Appetize**

1\. We will need the "**Client ID**" and "**Client secret**" for the app you just created.

<figure><img src="/files/UArbzZD0h6u5do0DoIO1" alt="Example Credentials to provide to Appetize.io"><figcaption><p>Credentials to provide to Appetize.io</p></figcaption></figure>

2\. We will also need your **Metadata URI**, often called "Discovery URL". For example: <https://dev-548472.oktapreview.com/oauth2/default/.well-known/oauth-authorization-server>

In OKTA, this is available in Security -> API -> Authorization servers -> Choose Server.

If the metadata endpoint is not available, you may also specify the required fields below:

* **authorization\_endpoint**
* **token\_endpoint**
* **userinfo\_endpoint**
* **jwks\_uri**
* **issuer**
* **introspection\_endpoint**


# SAML

{% hint style="info" %}
*Every SSO provider is a little bit different. Please* [*contact us*](mailto:hello@appetize.io) *with any questions!*
{% endhint %}

## Configure app settings

Please ensure that the user's email address is sent as the username in the SAML response.

Please create group assignments for `appetize_developer` and `appetize_admin`, and assign to users appropriately. All users who have access to the SAML app, with no group assignments, will default to the user role. Please ensure these group assignments are passed in the SAML response as an attribute named `groups`.

| Field                                | Value                            |
| ------------------------------------ | -------------------------------- |
| SP Entity Id / Audience URI          | appetizeio-saml                  |
| Assertion Consumer Service URL (ACS) | TBD - provided by Appetize.io    |
| Recipient URL                        | N/A - leave blank or same as ACS |
| Destination URL                      | N/A - leave blank or same as ACS |

## **Information to provide to Appetize**

* **entryPoint** - the URL from your service provider that will initiate a login.
* **x509 certificate** used to sign SAML responses

{% hint style="info" %}
your provider may provide an **IdP metadata file** that contains both the **entryPoint** and the **x509 certificate**. You may send that file to us.
{% endhint %}

* authentication context (optional) - usually necessary for Microsoft ADFS
* **signature algorithm** (default SHA256)
* which **identify provider** are you using? (ADFS, OKTA, etc)

## Example Configuration with OKTA

<figure><img src="/files/aigpFblsMkLaK7n29Vs7" alt="Creating a SAML app"><figcaption><p>Creating a SAML app</p></figcaption></figure>

<figure><img src="/files/wovsm0b4cNEAXXlUhLZz" alt="General Settings - you may customize as you see fit"><figcaption><p>General Settings - you may customize as you see fit</p></figcaption></figure>

<figure><img src="/files/6w95dn7TLj60FA8HK0wz" alt="Enter the Single Sign On URL Provided by Appetize"><figcaption><p>Enter the Single Sign On URL Provided by Appetize.</p></figcaption></figure>

<figure><img src="/files/Cods41AjfQBvf6IDgwHX" alt="Save the IdP metadata file and send to Appetize.io"><figcaption><p>Save the IdP metadata file and send to Appetize.io</p></figcaption></figure>


# Azure Active Directory

Appetize supports Azure Active Directory as an SSO provider, using the SAML protocol.

## Prerequisites

Please have the **entity ID** (usually `appetizeio-saml`) and the **Assertion Consumer Service URL** (looks like `https://appetize.io/sso/example/cb`) you received from Appetize support.

## Azure Active Directory Setup

In Azure Active Directory, go to Enterprise Applications. Create a new application. Choose a name for it (e.g. Appetize) and select "Integrate any other application you don't find in the gallery (Non-gallery).

<figure><img src="/files/JJlbW9h7tVV9hwf7U7GH" alt="List of enterprise apps. Click &#x22;New Application&#x22;"><figcaption><p>List of enterprise apps. Click "New Application"</p></figcaption></figure>

<figure><img src="/files/R198dfETqS2JD03WXFfx" alt=""><figcaption><p>Choose a name, e.g. "Appetize" and choose Non-gallery</p></figcaption></figure>

With the App created, click on "App roles". Create a role named "Appetize Admin" with value `appetize_admin`, and another role named "Appetize Developer" with value `appetize_developer`.

<figure><img src="/files/s7rqTh4CHqYOLENHkHHx" alt=""><figcaption><p>Creating the appetize<em>admin app role. Repeat for appetize_developer.</em></p></figcaption></figure>

<figure><img src="/files/3FUzikEYW1zkttRHSdGD" alt=""><figcaption><p>Your app roles should look like this</p></figcaption></figure>

Return to "Enterprise Applications" and choose "Appetize". Click "Users and groups" to authorize logging in. Click "Add user/group" and choose from your organization's existing users or groups. Select a role (Appetize Admin, Developer, or User) as appropriate. Save the assignment.

<figure><img src="/files/vHBlHccPqEWyQ4NZrJHs" alt=""><figcaption><p>Go to Users and Groups for the Appetize application</p></figcaption></figure>

<figure><img src="/files/VSHlFtP2dXDs6C34rJXx" alt=""><figcaption><p>Choose users and/or groups and assign them to the Admin, Developer, or User role</p></figcaption></figure>

Click "Single sign-on" and click "SAML". Enter the entity ID and Assertion Consumer Service URL provided by Appetize support.

<figure><img src="/files/ywB0qfjAz4HGWbM8XkNd" alt=""><figcaption><p>Click SAML</p></figcaption></figure>

<figure><img src="/files/n01BcFmdU1bdwUxfoHqE" alt=""><figcaption><p>Enter values provided by Appetize support</p></figcaption></figure>

On the next page, click the edit button next to "Attributes & Claims". Click "Add a new claim". Name: groups, Source: attribute, Source attribute: user.assignedroles. Save the claim.

<figure><img src="/files/0EZXpdrR7AHinn9aSfot" alt=""><figcaption><p>Click Edit button next to Attributes &#x26; Claims</p></figcaption></figure>

<figure><img src="/files/KCra7Zs44NZZoMBksK5e" alt=""><figcaption><p>Click "Add new claim"</p></figcaption></figure>

<figure><img src="/files/EGp3paKWXgFsTMta2HSF" alt=""><figcaption><p>Name: groups. Source attribute: user.assignedroles</p></figcaption></figure>

Return the SAML page and download the **"Federation Metadata XML" file**. Send this file to Appetize support. Alternative, you may send the **Certificate (Base64)** and the **Login URL**.

<figure><img src="/files/CjhTq1BCF3Tj5AyzCs1z" alt=""><figcaption><p>Download the Federation Metadata XML and send to Appetize support</p></figcaption></figure>

Appetize will provision SSO for your account after receiving the information. If necessary, we may also schedule a call to test the integration.


# Google Workspace (GSuite)

Several of our customers use Google Workspace (GSuite) as their SSO provider. Appetize support Google Workspace as an SSO provider, using the SAML protocol.

For setup instructions, we recommend clients first follow our SAML instructions here:

{% content-ref url="/pages/3lL0B8KFkOeshqpkRKw0" %}
[SAML](/account/single-sign-on/saml)
{% endcontent-ref %}

Once basic SAML integration has been set up, there are a few additional steps required in order for a user's assigned roles to be contained in the SAML response from your server.

## SSO configuration in Google Workspace

<figure><img src="/files/CIms5vwyrpP6NoN7G9sT" alt=""><figcaption><p>ACS URL and Start URL will be provided by Appetize.io</p></figcaption></figure>

## Configure a new Role on the user object

<figure><img src="/files/NeSqD8pPVWdgljIdiwlj" alt=""><figcaption><p>Define the Appetize_role (or name it as you wish)</p></figcaption></figure>

## Map the newly defined role to the App attribute "groups"

<figure><img src="/files/gLCnvDUYEDPkkYb2HGic" alt=""><figcaption><p>Map user role to "groups" attribute</p></figcaption></figure>


# API Tokens

API Tokens allow your organization to authenticate automated workflows and access the Appetize REST API.

You can manage your organization's API tokens by navigating to [**Organization → API Tokens**](https://appetize.io/organization/api-tokens)

{% hint style="warning" %}
Tokens belong to the organization, not to individual users.&#x20;

If a user who generated a token is removed from the organization, the token continues to work.
{% endhint %}

***

## Generating an API Token

Admins can create a new token by selecting **Generate API Token**.

You will be asked to provide:

* **Label**\
  A short name that identifies how this token will be used, such as a CI system, a script, or an internal tool. The label helps you recognize activity created by this token. We recommend not reusing the same label twice.
* **Role**\
  Choose the level of access the token should have.\
  Pick the least-privileged role that matches your automation’s needs.

Select **Generate Token** to create it.

<figure><img src="/files/dpczQTBhoUWwolfdxZUC" alt=""><figcaption></figcaption></figure>

After the token is created:

* The full token appears once. Copy and save it securely, because it cannot be viewed again.
* All organization admins will receive an email with the creator, the time it was generated, and an obfuscated version of the token.

<figure><img src="/files/GdZK3kBsf8XH5u2EKWV8" alt=""><figcaption></figcaption></figure>

***

## Viewing Tokens

<figure><img src="/files/dTpmbAle4gC69czvJBFE" alt=""><figcaption></figcaption></figure>

Admins can view all API Tokens in the organization. Each token displays:

* **Label**\
  The name given when the token was created, useful for identifying where the token is used.
* **Token**\
  A partially masked version of the token, shown for reference only. The full token is never displayed again after creation.
* **Last used**\
  Indicates the most recent time the token authenticated a request.
* **Created by**\
  Shows who originally generated the token.&#x20;
* **Role**\
  Indicates whether the token was created with Developer or Admin privileges.
* **Delete icon**\
  Allows an admin to revoke the token.

***

## **Revoking Tokens**

To revoke a token, select the bin icon next to it.\
A confirmation dialog appears and you will be asked to type the token’s label to confirm the removal.

<figure><img src="/files/BcWXgs1CPeCDwljPYe4x" alt=""><figcaption></figcaption></figure>

Once a token is revoked, any integrations using this token will lose its functionality immediately.


# Session History

Session History provides a centralized view of sessions run in your organization. Use it to review recent activity, analyze usage, and troubleshoot issues.

You can access [Session History](https://appetize.io/sessions) directly from the application sidebar:

<figure><img src="/files/iwi6QeA8qlVU2W2YL3tT" alt=""><figcaption><p>Session History in application sidebar</p></figcaption></figure>

***

## **Session List**

<figure><img src="/files/my8Z0rvtJz73uLCTIVw4" alt=""><figcaption></figcaption></figure>

The list displays recent sessions with the newest session at the top.

* **Admins** can view all sessions in the organization.
* **Developers and regular users** see only their own sessions.

Each session row provides a quick overview of session context, including who ran it, which app and device were used, when it started, and any available logs or attachments.

### **Filtering Sessions**

Session History includes several filters to help you narrow the list of sessions.

#### **Search**

<figure><img src="/files/c5ogCWztvnDqNroOJynV" alt=""><figcaption><p>Search Field</p></figcaption></figure>

Use the search field to quickly find sessions by:

* **App ID**
* **App name**
* **Username**

#### **Date Range**

<figure><img src="/files/wrmQY9fHXh47abgFnJED" alt=""><figcaption><p>Date Range Picker</p></figcaption></figure>

Select a start and end date to focus on sessions from a specific period.\
The range is inclusive and supports selecting any period allowed by the interface.

#### **User**

A dedicated user filter allows you to refine sessions by who ran them.

* **Admins** can select one or multiple users.
* **Developers and regular users** are limited to viewing their own sessions.

#### **Device**

Filter sessions by device type, or choose **All devices** to include everything.

#### **Operating System**

Filter sessions by OS version, or choose **All OS Versions** to include all versions.

### **Exporting Sessions**

An **Export CSV** button allows you to download the sessions that match your current filters.\
The exported file includes all visible columns along with additional session fields that may not appear in the table.

***

## **Session Details**

<figure><img src="/files/0X6kto1cDstHos61U16u" alt=""><figcaption><p>Session Detail Row</p></figcaption></figure>

Each session row includes key information that helps you understand the context of the session:

* **User information**\
  Shows who initiated the session. This may be the authenticated user’s email, an API token label, or a descriptive label for anonymous sessions.
* **App details**\
  Information about the app, app group, build, or related metadata.
* **Device and OS**\
  The device model and operating system used during the session.
* **Date**\
  When the session took place.&#x20;
* **Queue time**\
  How long the session waited before starting.
* **Duration**\
  How long the session lasted.\
  Ongoing sessions may show **In progress**.
* **Attachments**\
  Icons linking to downloadable logs or other session files. Icons are grayed out if an attachment is not available.


# Reporting

By default, Appetize provides reports on a wide range of activities and fields to give users insight into what's happening within their instance.

{% content-ref url="/pages/SDUFt7hzBkAJIyXrCMUJ" %}
[Session History](/account/reporting/session-history)
{% endcontent-ref %}

{% content-ref url="/pages/KnCKIYCRzis3tr2PPx8M" %}
[Usage Summary](/account/reporting/usage-summary)
{% endcontent-ref %}


# Session History

Appetize provides monthly session history reports to give users insight into what's happening within their instance, including which logged-in user ran that session.

### With Reporting Page

You can access your session history by navigating to your [Reports Dashboard](https://appetize.io/reports) and selecting **download** under session logs after specifying the month that you are interested in:

<figure><img src="/files/ySFW5ZLvXY5ZvbocfwUN" alt=""><figcaption><p>Select "Download" under session logs after specifying the month you are interested in</p></figcaption></figure>

{% hint style="info" %}
All reports are downloaded in UTC (Coordinated Universal Time) times.
{% endhint %}

## Glossary

{% hint style="warning" %}
Please note, depending on the activity and actions you take within Appetize, not all fields may be populated in your reports.
{% endhint %}

| Field                         | Description                                                                                                                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **reportStartDate**           | The starting date of the report.                                                                                                                                                              |
| **reportEndDate**             | The ending date of the report.                                                                                                                                                                |
| **publicKey**                 | Public key of the app associated with the session                                                                                                                                             |
| **sessionRequested**          | <p>When the session was requested.<br><strong>NOTE</strong>: May differ from session start time if there was a queue</p>                                                                      |
| **startTime**                 | When the session started                                                                                                                                                                      |
| **userConnected**             | When the user connected to the streaming server - should be shortly after *startTime.*                                                                                                        |
| **frameTime**                 | When the user received the first frame from the streaming server.                                                                                                                             |
| **appLaunchTime**             | When the app was launched - should be similar to *frameTime.*                                                                                                                                 |
| **endTime**                   | When the session ended.                                                                                                                                                                       |
| **closeTime**                 | <p>Session end time plus time to shut down and reset the simulator.<br><strong>NOTE</strong>: This is what we bill on for our metered usage customers using our Starter or Premium Plans.</p> |
| **sessionLengthMilliseconds** | Duration of the session in milliseconds.                                                                                                                                                      |
| **sessionLengthSeconds**      | Duration of the session in seconds.                                                                                                                                                           |
| **sessionLengthMinutes**      | Duration of the session in minutes.                                                                                                                                                           |
| **referrer**                  | The URL that brought the user to the app page, or the URL that embedded the app.                                                                                                              |
| **referrerHostname**          | Hostname of the referrer.                                                                                                                                                                     |
| **token**                     | A unique random identifier for this streaming session.                                                                                                                                        |
| **clientIP**                  | IP address of the user running the session.                                                                                                                                                   |
| **clientLanguage**            | System Language of user running the session.                                                                                                                                                  |
| **clientUserAgent**           | The user agent string that identifies the user's browser.                                                                                                                                     |
| **versionCode**               | <p>Which build of the app was run.<br><strong>NOTE:</strong> <em>Starts at 1 and increments each time the app is updated.</em></p>                                                            |
| **deviceType**                | Type of the device being used for this session.                                                                                                                                               |
| **osVersion**                 | Operating System being used for this session.                                                                                                                                                 |
| **rotation**                  | What orientation the device was in on session start (e.g., portrait, landscape).                                                                                                              |
| **debug**                     | Whether debug logging was used.                                                                                                                                                               |
| **warmSession**               | Whether the session was served from a reserved device.                                                                                                                                        |
| **queued**                    | Whether the user was queued before the session started.                                                                                                                                       |
| **noVideo**                   | If video was disabled, usually for headless usage.                                                                                                                                            |
| **location**                  | The manually specified GPS location for the emulator/simulator, if provided.                                                                                                                  |
| **appetizeUser**              | Username of the user associated with the session, if logged in.                                                                                                                               |

{% hint style="info" %}
Should you have a desire to report on additional fields, please [reach out to us](mailto:hello@appetize.io) and we'll consider it for our roadmap.
{% endhint %}


# Usage Summary

Appetize provides daily and monthly usage reports to give users insight into what's happening within their instance.

### With Reporting Page

You can access your usage summary by navigating to your [Reports Dashboard](https://appetize.io/reports) and selecting **download** under:

* **Monthly usage report**
* **Daily usage report** after specifying the month that you are interested in e.g. May 2023

<figure><img src="/files/2clopV6IbkRNW0slqNNR" alt=""><figcaption><p>Select "Download" under Monthly or Daily usage report</p></figcaption></figure>

{% hint style="info" %}
All reports are downloaded in UTC (Coordinated Universal Time) times.
{% endhint %}

### With REST API

You can access your usage report via our REST API. For more information, see

{% content-ref url="/pages/-MJVCSOMbSlFIuVYMwAz" %}
[Usage summary](/rest-api/v1/usage-summary)
{% endcontent-ref %}

## Glossary

{% hint style="warning" %}
Please note, depending on the activity and actions you take within Appetize, not all fields may be populated in your reports.
{% endhint %}

| Field                      | Description                                                                                                                   |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **month**                  | The month being reported on                                                                                                   |
| **publicKey**              | The App's publicKey                                                                                                           |
| **numSessions**            | How many sessions were run that month/day on that publicKey                                                                   |
| **minutes**                | How many total minutes for that day/month                                                                                     |
| **platform**               | *iOS* or *Android*                                                                                                            |
| **name\_latest**           | <p>The current name of the app</p><p><br><strong>NOTE</strong>: May differ from when the app was run</p>                      |
| **appdisplayname\_latest** | <p>The current display name of the app</p><p><br><strong>NOTE</strong>: May differ from when the app was run</p>              |
| **bundle\_latest**         | <p>The current <em>bundleId</em> of the app</p><p><br><strong>NOTE</strong>: May differ from when the app was run</p>         |
| **note\_latest**           | <p>The current notes for the app from the dashboard</p><p><br><strong>NOTE</strong>: May differ from when the app was run</p> |

{% hint style="info" %}
Should you have a desire to report on additional fields, please [reach out to us](mailto:hello@appetize.io) and we'll consider it for our roadmap.
{% endhint %}


# Infrastructure

Follow Our Guides for Best Practices on Network Configuration and Explore Enterprise Hosting Options

## Learn more about

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Configure Network Access</td><td><a href="/files/neRQQe86e0qOCYBdh4XK">/files/neRQQe86e0qOCYBdh4XK</a></td><td></td><td></td><td><a href="/pages/mNqk5dK3KYekWgx4HVyT">/pages/mNqk5dK3KYekWgx4HVyT</a></td></tr><tr><td>Enterprise Hosting Options</td><td><a href="/files/6yUygVX24hzcoFMBQ5Oc">/files/6yUygVX24hzcoFMBQ5Oc</a></td><td></td><td></td><td><a href="/pages/VoGJxfa1SyVKbjduP2zG">/pages/VoGJxfa1SyVKbjduP2zG</a></td></tr></tbody></table>


# Configure Network Access

While many users enjoy hassle-free access to Appetize without any network adjustments, certain corporate security protocols may restrict connections.

For most users there is no need for any network configuration to be able to use Appetize. However, some company's security policies might restrict network access.

## Streaming Server Access

To make sure you can access our streaming servers from behind a firewall:

* [x] Identify your deployment type: Global Cloud or Enterprise Private Cloud Instance.
* [x] Find the IP Blocks used by our streaming servers:
  * For Global Cloud Instances, visit <https://appetize.io/ip-blocks>.
  * For Enterprise Private Cloud Instances, visit <https://custom.appetize.io/ip-blocks>, replacing 'custom' with your assigned domain.
* [x] Make sure that your network configuration doesn't restrict connectivity to all of the listed IPs.

## Web Server Access

To make sure you can access our web servers from behind a firewall:

* [x] Identify your deployment type: Global Cloud or Enterprise Private Cloud Instance.
* [x] Find your assigned domain name:
  * For Global Cloud Instances, this will be **appetize.io.**
  * For Enterprise Private Cloud Instances, this will be **custom.appetize.io** replacing 'custom' with your assigned domain.
* [x] Ensure all outgoing connections to your domain e.g. **appetize.io** have been whitelisted\*\*.\*\*

{% hint style="info" %}
For our Enterprise Private Cloud Instances, you can also configure which IPs have access to your private instances. [Contact us](https://appetize.io/contact-us) to learn more.
{% endhint %}

## Web Socket Access

Our system relies on WebSockets to communicate with our streaming servers. However, corporate web proxies may block WebSocket connections, preventing you to successfully make use of our service. To ensure proper setup, you can verify your connection using websites like <https://websocketstest.com/>.

## Storage and Resource Access

To ensure that all necessary static content, scripts, and uploaded resources (such as builds) can be accessed from behind a firewall, please whitelist the following URLs:

* [x] `appetizeio-static.s3.amazonaws.com`
* [x] `appetizeio.s3.amazonaws.com`
* [x] `js.appetize.io`

{% hint style="info" %}
For Enterprise Private Cloud Instances, the URLs will be specific to your organization. [Contact us](https://appetize.io/support-request) for more information.
{% endhint %}


# Enterprise Hosting Options

Discover the different Appetize Enterprise Hosting Options: Global Cloud, Private Cloud, and Self-Hosting (Including AWS)

Appetize offers multiple hosting options to cater to all the different needs and preferences of our Enterprise clients. Whether you're looking for the scalability of our Global Cloud, the enhanced privacy and security of a Private Cloud, or considering self-hosting, understanding the differences between these options is important for making an informed decision.

## Global Cloud

The Global Cloud option uses Appetize's shared cloud environment, offering a flexible and affordable way to manage iOS and Android devices. It's perfect for enterprises that need to quickly deploy and scale their mobile management solutions using our default customization options.

## Private Cloud

The Private Cloud option gives you a dedicated instance managed by Appetize on separate hardware, offering enhanced privacy and security. This solution allows for extensive customization, including custom device images and networking configurations. It's ideal for organizations that need higher security and want to tailor the environment to their specific needs.

## Self-Hosting

Self-Hosting lets enterprises manage their infrastructure using their own on-premises hardware or cloud services like AWS. This option is particularly appealing for organizations with very strict security requirements. While it offers the same level of customization as the Private Cloud, self-hosting involves significant management and setup costs. Enterprises must handle deployment, maintenance, security updates, and scaling themselves, leading to bigger overhead.

{% hint style="info" %}
Our team is currently working on providing an on-demand AWS solution, complete with an AMI image. To learn more about this, please [contact us](https://appetize.io/contact-us).
{% endhint %}

## Summary

<table><thead><tr><th width="158"></th><th>Global Cloud</th><th>Private Cloud</th><th>Self Hosted</th></tr></thead><tbody><tr><td>Environment</td><td>Shared cloud</td><td>Dedicated instance</td><td>On-premises or AWS</td></tr><tr><td>Security</td><td>ISO 27001 &#x26; SOC2 Type II</td><td>Physically segregated hardware</td><td>Client defined</td></tr><tr><td>Customization</td><td>Default configurations</td><td>Extensive, including custom device images</td><td>Extensive, including custom device images</td></tr><tr><td>Management</td><td>Managed by Appetize</td><td>Managed by Appetize</td><td>Self-managed; enterprises responsible for deployment, maintenance, updates, and scaling.</td></tr><tr><td>Ideal for</td><td>Rapid deployment and scalability with default customizations</td><td>Higher security levels and tailored customization needs.</td><td>Very strict security requirements and complete control over infrastructure, despite higher management and setup costs.</td></tr></tbody></table>


# JavaScript SDK

Our JavaScript SDK offers an API to programmatically interact with Appetize devices. This allows you to automate interactions with the device, verify app behavior, and more.

## Installation

First, load the JavaScript SDK by adding the following snippet to the `head` section of your page:

```html
<script>
(function () {const n = window,i = document,o = i.getElementsByTagName("script")[0],t = i.createElement("script");
(t.src = "https://js.appetize.io/embed.js"), (t.async = 1), o.parentNode.insertBefore(t, o);
const s = new Promise(function (e) {t.onload = function () {e();};});n.appetize = {getClient: function (...e) {
return s.then(() => n.appetize.getClient(...e));},};})();
</script>
```

### Embed your app

Add an `iframe` with an [Appetize embed URL](/platform/embedding-apps):

```html
<iframe
    id="appetize"
    src="https://appetize.io/embed/<BuildId|PublicKey>"
    width="378px" 
    height="800px" 
    frameborder="0" 
    scrolling="no"></iframe>
```

{% hint style="info" %}
We gave the iframe an id of `appetize`, but it can be anything you wish.
{% endhint %}

## Get the Client

The easiest way to get the client is by calling [`window.appetize.getClient(selector)`](/javascript-sdk/api-reference#getclient-selector).

This will return an Appetize client instance for the embed e.g.

```javascript
const client = await window.appetize.getClient("#appetize")
```

{% hint style="info" %}
We also support getting the client with an initial [configuration](/javascript-sdk/configuration) - for scenarios where the initial embed link might not be known on launching of the page (no `src` specified on the `iframe`) by making use of [`window.appetize.getClient(selector, config)`](/javascript-sdk/api-reference/initialization#getclient-selector-config).

This will return an Appetize client instance with the initial [configuration](/javascript-sdk/configuration) applied e.g.

```typescript
const client = await window.appetize.getClient("#appetize", {    
    buildId: '{buildId|publicKey}',
    device: 'iphone13pro',
    osVersion: '15.0'
    ...
})
```

The base URL can be changed with a `data-appetize-url` attribute, e.g. `data-appetize-url="https://sampledomain.appetize.io"`
{% endhint %}

## Starting a Session

You can start a session programmatically:

```javascript
const session = await client.startSession()
console.log('session started!')
```

or wait for the user to click "Tap to Play":

```javascript
client.on("session", session => {
    console.log('session started!')
})
```

Next we'll cover various ways you can configure the embed, such as the device or OS version.


# Configuration

With Appetize's configuration options, users can easily switch between different device and operating system versions, languages, and many other options in order to customize their experience.

You can configure the client to change the device, OS version, app, and various other options. This will set the configuration for the embed before the session starts, either programmatically or by user interaction.

```javascript
await client.setConfig({
    device: 'iphone11pro',
    osVersion: '15.0'
})
```

Additionally, you can provide these when starting a session programmatically:

```javascript
const session = await client.startSession({
    device: 'iphone11pro',
    osVersion: '15.0'
})
```

{% hint style="info" %}
Initial values may also be provided in the embed URL. See [Query Params Reference](/platform/query-params-reference).
{% endhint %}

## Configuration Options

### buildId

`string`

The buildId (previously known as publicKey) of the Appetize app that you wish to run.

### device

`string`

The device to run on. [See Devices & OS Versions](/features/devices-and-os-versions).

### osVersion

`string`

The operating system version on which to run your app. e.g. 11.4, 12.2, 13.3, 14.0

*Note: We recommend leaving this blank so it will always use our latest default for the device.*

### scale

`number | 'auto'`

Sets the scale of the device in the iframe.

If a number is provided it must be between 10 and 100. If `'auto'`, the device will scale up to fit inside the iframe.

### autoPlay

`boolean`

When true, starts streaming the app on device load. Default is `false`.

{% hint style="warning" %}
We recommend starting the session programmatically using `client.startSession()` instead as this could cause the session to start before the SDK is ready.
{% endhint %}

### adbShellCommand

`string`

&#x20;(Android only) Executes an `adb shell` command on the device.

### androidPackageManager

`boolean`

(Android only) Allows installation of additional APKs after app launch.

### appearance

`"dark" | "light"`

(iOS 13+ and Android 10+) Sets dark or light mode UI.

### audio

`boolean`

(Android Only) Enables audio playback on the device.

### codec

`"h264" | "jpeg"`

Set the video codec used for the stream. Default is `h264` if the browser supports it, otherwise falls back to `jpeg`.

### debug

`boolean`

When true, the session will listen for debug logs and emit them as a `log` event.

### deviceColor

`"black" | "white"`

Sets the color of the device chrome.

### disableVirtualKeyboard

`boolean`

(Android only) When true, disabled the onscreen keyboard.

### enableAdb

`boolean`

(Android only) Sets up an SSH tunnel to allow ADB connections to the emulator. SSH command and info can be found by accessing the [adbConnection](/javascript-sdk/api-reference#adbconnection) property on the session.

For more information see [ADB tunnel](/features/advanced-features/android/adb-tunnel).

### grantPermissions

`boolean`

Automatically grant all required app permissions. [See Auto-grant permissions](/features/auto-grant-permissions).

### hidePasswords

`boolean`

(Android only) Hide password visibility when typing.

### iosKeyboard

`string`

Set the language for the iOS Keyboard. eg. `ja_JP@sw`. [See available values](https://pgssoft.github.io/AutoMate/Enums/SoftwareKeyboard.html).

### iosAutocorrect

`boolean`

Turn on Auto-Correction for iOS. Defaults to `true`.

### language

`string`

Sets the language of the device. Must be an [ISO 639-1 & BCP 47](https://stackoverflow.com/questions/7973023/what-is-the-list-of-supported-languages-locales-on-android) language code.

### launchApp

`boolean | string`

Indicates whether an app launches after installation and allows specifying which installed app to open.

**Possible values:**

* `false` - Apps install but do not launch.
* `true` or `undefined` – Default behavior
* `appId` - Launches the app with the specified [app Identifier](/platform/sharing-apps#app-identifier) (e.g., `com.android.chrome`).

### launchUrl

`string`

Specify a deep link to open when your app is launched.

### launchArgs

`string[]`

(iOS only) An array of strings to pass when launching your app.

### locale

`string`

(iOS only) Sets the locale of the device. Must be a locale ID e.g. `en_GB`, `fr_FR`.

### location

`number[]`

(iOS 12+, Android 10+) Sets location of the device in latitude and longitude. e.g.. \[`39.903924,116.391432]`

### noVideo

`boolean`

Sets whether the video feed is enabled.

### orientation

`"portrait" | "horizontal"`

Sets the orientation of the device

### platform

`'ios' | 'android'`

Sets the platform of the device.

### params

`object`

A JSON object that will be passed to your app on launch

### plistEdit

`Object`

Represents an object that allows additional key-value properties to be added to the app's plist, enabling customization or configuration of the app behavior during launch.

### proxy

`string`

Specify a proxy server to route network traffic. eg `http://example.com:8080`

For Appetize's built-in intercepting proxy, use `intercept`. Network logs are emitted from the session as a `network` event.

{% hint style="info" %}
Our current support is limited to HTTP Proxies. When your app makes HTTPS connections, the data remains encrypted despite the unencrypted connection to the proxy. The app sends a CONNECT request to the proxy for the destination HTTPS server, initiating an SSL handshake. The proxy acts as a TCP connection forwarder, ensuring end-to-end encryption for app data.
{% endhint %}

### record

`boolean`

Enables recording of all user actions that took place during the session. See [UI Automation](/features/ui-automation) for more information. Default is true.

### region

`us` | `eu`

Ensures that Appetize sessions are launched only from servers in a specific region.

{% hint style="warning" %}
It is best to avoid setting this property unless absolutely necessary. Our system automatically directs requests to the closest servers for optimal performance. Using this property could lead to longer queues in busy regions.
{% endhint %}

### screenOnly

`boolean`

If true, only show the screen and not the device chrome.

### showRotateButtons

`boolean`

Enables the display of rotate buttons next to the device. Requires `scale` to be set to `auto`.

### timezone

`string`

(Android only) Sets the timezone of the device. [See available values](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).

### endSessionRedirectUrl

`string`

Specifies the URL to redirect users to at the end of the session.&#x20;

### userInteractionDisabled

`boolean`

Sets whether user interaction is disabled.

### volume

`number`

Sets the audio playback level. The value ranges from `0` to `1`, defaults to `0.5`


# Automation

The device can be interacted with programmatically through our API. This is useful for scenarios such as logging in a user at the start of a session, or writing tests.

See [UI Automation](/features/ui-automation) for more information.

### Next Steps

{% content-ref url="/pages/r3JFMcOkR53HGkAk9iWP" %}
[Device commands](/javascript-sdk/automation/device-commands)
{% endcontent-ref %}

{% content-ref url="/pages/SmU2Ki3FbZJAJGfHTlGZ" %}
[Touch interactions](/javascript-sdk/automation/touch-interactions)
{% endcontent-ref %}

{% content-ref url="/pages/7t40kUkClxzgTv85QfGt" %}
[API reference](/javascript-sdk/api-reference)
{% endcontent-ref %}


# Device commands

The client provides methods to configure the device and start a session, while the session provides methods for user interaction.

## Client

### startSession()

Starts a session with the requested app, device, operating system, and other launch options.

```typescript
const session = await client.startSession()
```

**Parameters**

| Name      | Type                  | Description                                                                                                               |
| --------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `config?` | `Record<string, any>` | A JSON object describing the [Configuration options](/javascript-sdk/configuration#configuration-options) for the device. |

### setConfig()

Update the configured app, device, operating system, or other launch options. See [Configuration](/javascript-sdk/configuration#configuration-options) for acceptable values.

*Note: This will end any active sessions.*

```typescript
await client.setConfig(config)
```

### endSession()

Ends the active session or cancels any pending session requests.

```typescript
await client.endSession()
```

## **Session**

### adbShellCommand()

Executes an `adb shell` command on the device (Android only)

{% code overflow="wrap" %}

```typescript
await session.adbShellCommand("am start -a android.intent.action.VIEW -d https://appetize.io/")
```

{% endcode %}

### allowInteractions()

Enables or disables all interactions on the device.

```typescript
await session.allowInteractions(false)
```

### biometryEnrollment()

Sets the biometry enrollment status (*iOS Only*)

```typescript
await session.biometryEnrollment(true/false)
```

### biometry()

Simulate a matching fingerprint (Android 8+ only) or Face ID (iOS)

```typescript
await session.biometry({
    match: true/false
})
```

### end()

Ends the session

```typescript
await session.end()
```

### getUI()

{% hint style="warning" %}
**Experimental**\
The data structure of the response is subject to change
{% endhint %}

Returns an array of elements describing the current UI on the device.

```typescript
const ui = await session.getUI()

/*
[
  { type: 'app', appId: 'com.my.app', children: [...] },
  { type: 'app', appId: 'com.apple.springboard', children: [...] }
]
*/

```

### heartbeat()

Sends a heartbeat to the server, resetting the inactivity timer of the session

```typescript
await session.heartbeat()
```

### keypress()

Sends a single key press to the device.

```javascript
await session.keypress("a")
```

This can also be used to send hardware and text-editing keys:

* `Backspace`  Delete a type character
* `HOME` Navigate to the home screen.
* `VOLUME_UP` (Android)
* `VOLUME_DOWN` (Android)
* `ANDROID_KEYCODE_MENU` (Android)
* `LOCK_SCREEN` (Android)
* `UNLOCK_SCREEN` (Android)
* `TOGGLE_SCREEN_LOCK` (iOS)

### openUrl()

Opens a deep-link or web URL

```typescript
await session.openUrl("https://appetize.io")
```

### launchApp(appId)

Launches the specified application using the provided `appId`.

{% hint style="info" %}
If the app is already running, it will be brought to the foreground instead of being relaunched. If the app was originally launched with params or a launchUrl, these will also be passed with this method.
{% endhint %}

```typescript
await session.launchApp(appId)
```

**Parameters**

| Name    | Type     | Description                                                                                                                                                                                                                                                                                                                |
| ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appId` | `string` | <p><strong>Android:</strong><br>The app's package name / appId (e.g., <code>com.example.app</code>) or <code>packageName/activityName</code>. If no activity name is specified, it defaults to the main launch activity.<br><strong>iOS:</strong><br>The app's bundle identifier (e.g., <code>com.example.app</code>).</p> |

### restartApp()

Restarts the app

```typescript
await session.restartApp()
```

### reinstallApp()

Reinstalls the app

```typescript
await session.reinstallApp()
```

### rotate()

Rotates the device 90 degrees left or right

```typescript
await session.rotate('left')

await session.rotate('right')
```

### screenshot()

Takes a screenshot of the device and returns the data as a buffer.

```typescript
const { data, mimeType } = await session.screenshot()
```

Alternatively, it can return the data as a base64 encoded string

```javascript
const { data, mimeType } = await session.screenshot('base64')
```

### shake()

Shakes device

```typescript
await session.shake()
```

### toggleSoftKeyboard()

Toggles the soft keyboard (iOS only)

```typescript
await session.toggleSoftKeyboard()
```

### setLanguage()

Changes the current language.

```typescript
await session.setLanguage("fr")
```

{% hint style="warning" %}
If your app does not automatically handle language/locale changes, you would need to explicitly call [restartApp](#restartapp) for this to take effect.\
\
Some apps might also cache data in the previously used language. In these cases use [reinstallApp](#reinstallapp) to clear any previous cached data.
{% endhint %}

### type()

Types the given text

```javascript
await session.type("hello")
```

{% hint style="warning" %}
Typing is limited to 1000 characters at a time to ensure optimal performance and prevent potential disruptions. For larger payloads, you can use multiple 'type' operations.
{% endhint %}

{% hint style="info" icon="lightbulb-exclamation-on" %}
**Tip:** Need to delete a character after typing? Use `keypress`:

```
await session.keypress('Backspace')
```

{% endhint %}

### addMedia(file)

{% hint style="info" %}
The maximum file size for uploading media is **50 MB**.
{% endhint %}

Upload media to the device.

```typescript
await session.addMedia(file)
```


# Touch interactions

## Targeting Elements

Touch interactions may accept a target element to play the interaction on. An element is described with an "Element Selector"; a convenient to way to target a UI element on the device.

An Element Selector describes the element in your application by its attributes and other properties. Below is a list of accepted attributes for each platform.

{% hint style="info" %}
Element Selectors work regardless of the device or screen size, meaning you can run the same set of actions on both a phone and tablet
{% endhint %}

### Element Attributes

{% tabs %}
{% tab title="iOS" %}

| Attribute               | Description                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| accessibilityIdentifier | The element's [accessibilityIdentifier](https://developer.apple.com/documentation/uikit/uiaccessibilityidentification/1623132-accessibilityidentifier) |
| accessibilityLabel      | The element's [accessibilityLabel](https://developer.apple.com/documentation/objectivec/nsobject/1615181-accessibilitylabel)                           |
| accessibilityHint       | The element's [accessibilityHint](https://developer.apple.com/documentation/objectivec/nsobject/1615093-accessibilityhint)                             |
| accessibilityValue      | The element's [accessibilityValue](https://developer.apple.com/documentation/objectivec/nsobject/1615117-accessibilityvalue)                           |
| text                    | Text content of the element and its children (case sensitive)                                                                                          |
| {% endtab %}            |                                                                                                                                                        |

{% tab title="Android" %}

| Attribute     | Description                                                                           |
| ------------- | ------------------------------------------------------------------------------------- |
| resource-id   | <p>Value of the element's Resource ID<br><br>ex: <code>com.example:id/icon</code></p> |
| content-desc  | Value of the element's Content Description (case sensitive)                           |
| text          | Text content of the element and its children (case sensitive)                         |
| {% endtab %}  |                                                                                       |
| {% endtabs %} |                                                                                       |

Any mixture of these attributes can be used to describe your element:

```javascript
// tap on an element by text
await session.tap({
  element: {
    attributes: { 
      text: "OK"
    }
  }  
})

// tap on an element by accessibilityIdentifier
await session.tap({
  element: {
    attributes: {
      accessibilityIdentifier: "dialog-confirm-button"
    }
  }  
})

// tap on an element by *both* text and accessibilityIdentifier
await session.tap({
  element: {
    attributes: {
      text: "OK",
      accessibilityIdentifier: "dialog-confirm-button"
    }
  }  
})
```

## Best Practices

When developing your app, we recommend adding accessibility identifiers wherever possible to aid you when automating interactions with Appetize. This will allow for simpler queries and also help your app be more accessible.

### iOS

On iOS, you should be using **`accessibilityIdentifier`**.

### Android

On Android, you should be using **`resource-id`**.

### React Native

On React Native, a **`testID`** property on your component will map to **`accessibilityIdentifier`** on iOS and **`resource-id`** on Android.

{% hint style="warning" %}
Some components may not (yet) support mapping **testID** back to a **resource-id**. In these cases the fallback is to look at **tag** or **content-desc** for the value.
{% endhint %}

### Sample

```javascript
// ios
await session.tap({
  element: {
      attributes: {
          accessibilityIdentifier: "dialog-confirm-button"
      }
  }
})

// android
await session.tap({
  element: {
      attributes: {
          'resource-id': "dialog-confirm-button"
      }
  }
})
```

{% hint style="info" %}
If you do not have an **accessibilityId** to reference it is best to query elements by text or accessibility attributes such as **`accessibilityLabel`** (iOS) or **`content-desc`** (Android).
{% endhint %}

## Methods

### findElement

Returns an element that matches the selector. This is useful for waiting until an element appears.

```javascript
const element = await session.findElement({
    attributes: {
        text: "Home"
    }
})

console.log(element)

// output
{
  attributes: { text: "Home", class: "UILabel" },
  path: "0/0/0/0/1/2/3",
  bounds: { x: 10, y: 100, width: 100, height: 20 }  
}
```

If multiple elements are found it will return the first element.

### findElements

Returns an array of all elements matching that selector.

```javascript
const elements = await session.findElements({
    attributes: { 
       accessibilityLabel: 'Like post'
    }
})
```

### swipe

Swipes from the screen position or element

#### By position

```javascript
// swipe up at middle of the screen
session.swipe({
    gesture: 'up', // can be 'left', 'down', 'right', or a function
    position: {
        x: '50%', // 50% of screen width
        y: '50%', // 50% of screen height
    },
    duration: 1000 // optional, in ms
})
```

#### By element

```javascript
// swipe left from the middle of an image element
session.swipe({
    gesture: 'left'
    element: { 
        attributes: {
            accessibilityIdentifier: 'image-carousel' 
        }        
    },
    // optional, defaults to 50%,50%
    localPosition: { 
        x: '50%', // middle of element on x-axis
        y: '50%'  // middle of element on y-axis
    }
})
```

#### Complex Gestures

You can describe a more complex gesture by providing a function to `gesture`.

```javascript
// swipe from middle of screen to the left and then down
session.swipe({
    position: {
        x: '50%',
        y: '50%',
    },
    // use up(), left(), right(), or down() for simple movements
    gesture: g => g.left('25%').down('25%')
})

// swipe diagonally up and right
session.swipe({
    position: {
        x: '50%',
        y: '50%',
    },
    // .to(x, y) will move to that position on screen
    gesture: g => g.to('25%', '-25%')
})

// swipe up, wait 500ms, and then swipe right
session.swipe({
    position: {
        x: '0%',
        y: '100%',
    },
    // wait(ms) will hold the swipe gesture in place
    gesture: g => g.up('25%').wait(500).right('50%')
})
```

### tap

Taps on the specified element or coordinates

{% hint style="info" %}
You can set a duration for a tap to simulate holding it down. For example, if you set it to 2 seconds, the tap will stay pressed for 2 seconds before releasing.

```typescript
await session.tap({ element, duration: 2000 })
```

{% endhint %}

#### By position or coordinate

```javascript
// tap at middle of the screen
await session.tap({
    position: {
        x: '50%',
        y: '50%'
    },
    // optional, defaults to 50%,50%
    localPosition: { 
        x: '50%', // middle of element on x-axis
        y: '50%'  // middle of element on y-axis
    }
})

// tap at x/y coordinate
await session.tap({
    coordinates: {
        x: 100,
        y: 250
    },
    // optional, defaults to 50%,50%
    localPosition: { 
        x: '50%', // middle of element on x-axis
        y: '50%'  // middle of element on y-axis
    }
})
```

#### By element

```javascript
await session.tap({ 
    element: {
        attributes: {
            text: 'submit'
        } 
    },
    // optional, defaults to 50%,50%
    localPosition: { 
        x: '50%', // middle of element on x-axis
        y: '50%'  // middle of element on y-axis
    }
})
```

## Timeouts

Interactions that target an element will wait up to 10 seconds to find the element. You may lower these timeouts by passing a second parameter:

```javascript
await session.tap({ ... }, { timeout: 5000 })
await session.swipe({ ... }, { timeout: 5000 })
await session.findElement({ ... }, { timeout: 5000 })
```


# Automation Engine - Migration Guide

Migrating to the New Appetize Automation Engine

We’re excited to introduce our **new automation engine**.\
This update brings improved **stability**, **stronger integration with Appetize**, and full support for **modern frameworks** like **SwiftUI**, **Flutter**, **Compose Multi-Platform, Jetpack Compose,** **React Native** and more.&#x20;

It’s largely **backward-compatible with existing automation flows**, so in most cases your existing **JS SDK commands will continue to work without changes**.

***

## :sparkles: What’s New

### Better Integration Across Appetize

This engine is more tightly integrated with Appetize services, giving us a consistent foundation for all automation features.\
It also makes it easier for us to add new commands, selectors, and advanced testing capabilities in the future.

### Declarative UI Framework Support

Apps built with frameworks such as **SwiftUI**, **Jetpack Compose**, **Flutter and more** are now fully supported.\
In older versions, these apps often appeared as a single, non-interactive surface. The new engine correctly exposes their underlying UI structure, allowing tests to target and interact with individual elements.

### More Stability and Resilience

The new engine is **more forgiving of small UI changes** and **handles variations in layout or timing more smoothly**.

### Regex Support

You can now use **regular expressions** in text selectors for flexible matching.\
This is helpful when text values change dynamically, such as in localized or data-driven UI.

***

## :dart:  Main Areas to Focus On

Most existing automations will continue to work, but there are a few key areas to review when testing.

### 1. Remove UIKit/Android Specific Selectors

The new engine continues to support **accessibility-based selectors** - the same pattern many teams already use - but now makes this the **recommended and primary approach**.

{% hint style="info" %}
Prefer using **accessibility elements** such as labels, identifiers, or visible text.\
They’re stable across app frameworks and align with how modern apps expose their UI.
{% endhint %}

In previous versions, the engine also exposed some platform-specific attributes. These are no longer available:

* **iOS (UIKit):** `class`, `baseClass`, `isHidden`
* **Android:** `className`

See our[ Selectors Reference](/javascript-sdk/automation/touch-interactions#targeting-elements) for guidance and examples.

***

### 2. Text Resolution Changes

If an element has both a `text` value and an `accessibilityLabel`, the label will now **override** the text.\
If `text` is empty, the engine falls back to `accessibilityText`.

```swift
TextField("placeholder", text: $value)
  .accessibilityLabel("My new placeholder text")
// Result: text == "My new placeholder text"

```

{% hint style="info" %}
To capture the raw value instead of the accessibility label, use\
`accessibilityValue` or `accessibilityTitle`.
{% endhint %}

***

### 3. Visibility Semantics

Elements that are off-screen but rendered (e.g., in a `UIStackView` outside the viewport)\
are **no longer reported as interactable**. Scroll to bring them into view before interacting.

{% code title="Scroll first, then tap" %}

```js
await session.swipe({ gesture: 'up', duration: 600 });
await session.tap({
  element: { attributes: { text: 'Past Articles' } }
});
```

{% endcode %}

***

### 4. WebView Selectors

* `id` attributes are not currently exposed for WebViews.
* Use **ARIA** attributes (like `aria-label`) or visible text content instead.

{% code title="Example WebView content" %}

```html
<button role="button" aria-label="Continue checkout">Checkout</button>
```

{% endcode %}

{% code title="Test targeting by visible text" %}

```js
await session.tap({
  element: { attributes: { text: 'Continue checkout' } }
});
```

{% endcode %}

***

### 5. Timeouts

Timeouts are now **best-effort** rather than exact.\
If a check is already in progress, a 1 s timeout may complete slightly later.

```js
await session.waitForAnimations({ timeout: 1000 }); // may finish ~1.5 s
```

***

### 6. `getUI` Response Change

The `getUI` response is now simplified to focus on **what’s visible to the user**.

Previously, it returned both the running app **and** the **Springboard** (system) hierarchies.\
Now, you’ll still see the same top-level structure for compatibility, but only the **first app node** contains content.\
Springboard will be present but empty.

{% tabs %}
{% tab title="Before" %}
App and system content were separated into two distinct sections.

```json
[
  {
    "type": "app",
    "appId": {running app},
    "children": [ ... ] // Your app content
  },
  {
    "type": "app",
    "appId": "com.apple.springboard",
    "children": [ ... ] // Springboard (system UI) content
  }
]
```

{% endtab %}

{% tab title="After" %}
A single combined hierarchy is returned, representing what’s actually visible to the user.

```json
[
  {
    "type": "app",
    "appId": {possible appId},
    "children": [ ... ] // All visible content on screen (e.g. Springboard related content too)
  }
]
```

{% endtab %}
{% endtabs %}

This structure makes the UI tree easier to reason about and aligns with what appears on screen.

***

## 💬 Feedback

This rollout marks a major step toward **stable, integrated, and cross-platform automation** within Appetize. If you encounter any unexpected behaviors or migration challenges, please [reach out to us](mailto:hello@appetize.io). Your feedback helps us continue improving and expanding what’s possible with Appetize automations.


# API reference

Appetize JavaScript SDK API Reference of All Available Methods and Properties

<table data-view="cards"><thead><tr><th></th><th data-hidden></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Initialization</td><td></td><td></td><td><a href="/pages/ltzI1I58jflqRDm5RwJK">/pages/ltzI1I58jflqRDm5RwJK</a></td><td><a href="/files/MJkZloLEUGSILEZpAaXV">/files/MJkZloLEUGSILEZpAaXV</a></td></tr><tr><td>Client</td><td></td><td></td><td><a href="/pages/ObWoy02a2Mrc09YIG8su">/pages/ObWoy02a2Mrc09YIG8su</a></td><td><a href="/files/QNrW3rv7BcGhozvQ836h">/files/QNrW3rv7BcGhozvQ836h</a></td></tr><tr><td>Session</td><td></td><td></td><td><a href="/pages/pykousSQWFygNgCuA8md">/pages/pykousSQWFygNgCuA8md</a></td><td><a href="/files/5pdfvZuAGla2YkafhlRc">/files/5pdfvZuAGla2YkafhlRc</a></td></tr></tbody></table>


# Initialization

Obtain an Appetize client instance using one of the getClient methods.

## getClient(selector)

Get an instance of the Appetize client.

{% hint style="warning" %}
When using `getClient(selector)`, you **must set** the iframe’s `src` attribute *before* calling `getClient` to configure the initial session.
{% endhint %}

```javascript
const client = await window.appetize.getClient('#my_iframe')
```

**Parameters**

| Name     | Type     | Description                                             |
| -------- | -------- | ------------------------------------------------------- |
| selector | `string` | A query selector string pointing to the embedded iframe |

## getClient(selector, config)

Get an instance of the Appetize client as well as set the initial config when loading the client.

*Useful when the embed link might not be known up front and the configuration has to be applied at runtime.*

{% hint style="warning" %}
When using `getClient(selector, config)`, **do not set** `iframe.src`.\
Setting the `src` manually may cause a race condition where the iframe loads before the SDK connects.\
\
For Private Cloud embeds, use `data-appetize-url` on the iframe instead of `src`.
{% endhint %}

```javascript
const client = await window.appetize.getClient('#my_iframe', {
    buildId: '{buildId|publicKey}',
    device: 'iphone11pro',
    osVersion: '15.0'
    ...
})
```

**Parameters**

| Name     | Type                                                               | Description                                                                                                               |
| -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| selector | `string`                                                           | A query selector string pointing to the embedded iframe                                                                   |
| config   | [SessionConfig](/javascript-sdk/api-reference/types/sessionconfig) | A JSON object describing the [Configuration options](/javascript-sdk/configuration#configuration-options) for the device. |


# Client

The client provides methods to configure the embedded device, manage sessions and listen to device related events.

## Methods <a href="#on-client" id="on-client"></a>

### on(event, listener) <a href="#on-client" id="on-client"></a>

Listens for an event of the given name

```typescript
client.on(event, data => {
   console.log(data)
})
```

<table data-full-width="false"><thead><tr><th>Event</th><th>Data Type</th><th>Description</th></tr></thead><tbody><tr><td><h4><strong>app</strong></h4></td><td><a href="/pages/f29RUhLUNqUwIdvz23tc">AppetizeApp</a></td><td>Emitted when the loaded <a href="#app-1">Appetize app</a> changes. Read the initial app from <code>client.app</code>.</td></tr><tr><td><h4><strong>deviceInfo</strong></h4></td><td><a href="/pages/4Oh4hmuYgQZh9vQvHyUw">DeviceInfo</a></td><td>Emitted when the current <a href="#device">device</a> changes. Read the initial device from <code>client.device</code>.</td></tr><tr><td><h4><strong>error</strong></h4></td><td><code>{ message: string }</code></td><td>An error has occurred. Use this event to display or record client errors.</td></tr><tr><td><h4><strong>queue</strong></h4></td><td><code>{</code><br><code>type: "session | concurrent",</code><br><code>position: number,</code><br><code>name: string</code><br><code>}</code></td><td><p>Your position in queue for the device.</p><ul><li><strong>concurrent</strong>: You've reached the max concurrent sessions for your account and are waiting for the next available slot. The concurrent queue <strong>name</strong> will be shown.</li><li><strong>session</strong>: You're in a queue, waiting for the next available device.</li></ul></td></tr><tr><td><h4>queueEnd</h4></td><td><code>void</code></td><td>The active queue has ended.</td></tr><tr><td><h4><strong>session</strong></h4></td><td><code>Session</code></td><td>A new <a href="/pages/pykousSQWFygNgCuA8md">session</a> has started either by the client or user clicking "Tap to Play"</td></tr><tr><td><h4><strong>sessionEnded</strong></h4></td><td><code>void</code></td><td>The active session has ended.</td></tr><tr><td><h4><strong>sessionError</strong></h4></td><td><code>Error</code></td><td>A session was served but failed to reach a ready state.</td></tr><tr><td><h4><strong>sessionRequested</strong></h4></td><td><code>void</code></td><td>A new session has been requested either by the client or user clicking "Tap to Play"</td></tr></tbody></table>

{% hint style="info" %}
See [Handle session startup failures](/guides-and-samples/handle-session-startup-failures) for custom error UI and retry patterns.
{% endhint %}

### startSession()

Starts a session with the requested app, device, operating system, and other launch options.

```typescript
const session = await client.startSession()
```

**Parameters**

| Name      | Type                                                               | Description                                                                                                               |
| --------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `config?` | [SessionConfig](/javascript-sdk/api-reference/types/sessionconfig) | A JSON object describing the [Configuration options](/javascript-sdk/configuration#configuration-options) for the device. |

### setConfig()

Update the configured app, device, operating system, or other launch options.

*Note: This will end any active sessions.*

```typescript
await client.setConfig(config)
```

**Parameters**

| Name     | Type                                                               | Description                                                                                                               |
| -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `config` | [SessionConfig](/javascript-sdk/api-reference/types/sessionconfig) | A JSON object describing the [Configuration options](/javascript-sdk/configuration#configuration-options) for the device. |

### getConfig()

Returns the current config

```javascript
const config = client.getConfig()
```

### endSession()

Ends the active session or cancels any pending session requests.

```typescript
await client.endSession()
```

## Properties

### device

The initially loaded device. It updates when the device changes. See [DeviceInfo](/javascript-sdk/api-reference/types/deviceinfo).

### app

The initially loaded app. It updates when the app changes. See [AppetizeApp](/javascript-sdk/api-reference/types/appetizeapp).


# Session

The Session in Appetize makes it easy to manage and interact with device sessions, including simulating user actions, toggling device states,  retrieving device information and more.

## Methods <a href="#on-session" id="on-session"></a>

### on() <a href="#on-session" id="on-session"></a>

Listens for an event of the given name

```typescript
session.on(event, data => {
   console.log(data)
})
```

| Event                                        | Data Type                                                                                                                                                                | Description                                                                                                                                                                                                                                                                                                   |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <h4><strong>action</strong></h4>             | [RecordedAction](/javascript-sdk/api-reference/types/recordedaction)                                                                                                     | <p>A user action has been recorded. This can be played back later with <a href="#playaction-action-options">playAction</a>.<br><br>Requires <a href="/pages/IsSvj9zoeKGvW97QzAXX#record">record</a> to be set to <code>true</code></p>                                                                        |
| <h4>appLaunch</h4>                           | `void`                                                                                                                                                                   | App launch event occurred.                                                                                                                                                                                                                                                                                    |
| <h4><strong>audio</strong></h4>              | <p><code>{</code><br><code>buffer: Uint8Array, codec: 'aac',</code><br><code>duration: number</code><br><code>}</code></p>                                               | <p>Audio frames of the current session.<br><br>Requires <a href="/pages/IsSvj9zoeKGvW97QzAXX#audio">audio</a> to be set to <code>true</code></p>                                                                                                                                                              |
| <h4><strong>error</strong></h4>              | `{ message: string }`                                                                                                                                                    | An error has occurred on the session                                                                                                                                                                                                                                                                          |
| <h4>firstFrameReceived</h4>                  | `void`                                                                                                                                                                   | First video frame received.                                                                                                                                                                                                                                                                                   |
| <h4><strong>inactivityWarning</strong></h4>  | `{ secondsRemaining: number }`                                                                                                                                           | <p>Session is about to timeout due to inactivity.</p><p>Any user interaction or a <a href="#heartbeat">heartbeat</a> will reset the timeout.</p>                                                                                                                                                              |
| <h4><strong>interaction</strong></h4>        | [UserInteraction](/javascript-sdk/api-reference/types/userinteraction)                                                                                                   | User has interacted with the device.                                                                                                                                                                                                                                                                          |
| <h4><strong>log</strong></h4>                | `{ message: string }`                                                                                                                                                    | <p>Debug log from the device<br><br>Requires <a href="/pages/IsSvj9zoeKGvW97QzAXX#debug">debug</a> to be set to <code>true</code></p>                                                                                                                                                                         |
| <h4><strong>network</strong></h4>            | [NetworkRequest](/javascript-sdk/api-reference/types/networkrequest) \| [NetworkResponse](/javascript-sdk/api-reference/types/networkresponse)                           | <p>Intercepted network request or responses.<br></p><p>Requires <a href="/pages/IsSvj9zoeKGvW97QzAXX#proxy">proxy</a> to be set to <code>intercept</code></p>                                                                                                                                                 |
| <h4><strong>orientationChanged</strong></h4> | `'portrait' \| 'landscape'`                                                                                                                                              | The device has changed orientation                                                                                                                                                                                                                                                                            |
| <h4><strong>video</strong></h4>              | <p><code>{</code><br><code>buffer: Uint8Array,</code><br><code>width: number,</code><br><code>height: number,</code><br><code>codec: string</code><br><code>}</code></p> | <p>Video frames of the current session.<br><br>These frames can be muxed (e.g. using <a href="https://github.com/samirkumardas/jmuxer">jmuxer</a>) to turn it into a video format.<br><br>When <a href="/pages/IsSvj9zoeKGvW97QzAXX#codec">codec</a> is <code>jpeg</code> the buffers are of jpeg images.</p> |
| <h4><strong>end</strong></h4>                | `void`                                                                                                                                                                   | The session has ended                                                                                                                                                                                                                                                                                         |

### end()

Ends the session

```typescript
await session.end()
```

### rotate()

Rotates the device 90 degrees

```typescript
await session.rotate('right')
```

**Parameters**

| Name      | Type                | Description             |
| --------- | ------------------- | ----------------------- |
| direction | `"left" \| "right"` | The direction to rotate |

### screenshot(format)

Takes a screenshot of the device and returns the data

```typescript
const { data, mimeType } = await session.screenshot(format)
```

**Parameters**

| Name    | Type                   | Description                                                       |
| ------- | ---------------------- | ----------------------------------------------------------------- |
| format? | `"buffer" \| "base64"` | The format of the screenshot data to return. Defaults to `buffer` |

### heartbeat()

Sends a heartbeat to the server, resetting the inactivity timer

```typescript
await session.heartbeat()
```

### tap(target, options)

Taps on the screen at the given position, coordinate or element

```typescript
await session.tap({ position: { x: '50%', y: '50%' } })
await session.tap({ coordinates: { x: 100, y: 100 } })
await session.tap({ element: { attributes: { text: 'OK' } } })
```

**Parameters**

| Name                | Type                                                                                           | Description                                                                                 |
| ------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| target.coordinates? | <p><code>{</code><br><code>x: number</code><br><code>y: number</code></p><p><code>}</code></p> | The coordinates in dip units                                                                |
| target.position?    | <p><code>{</code><br><code>x: string</code><br><code>y: string</code><br><code>}</code></p>    | The position on screen in %                                                                 |
| target.element?     | `ElementSelector`                                                                              | An [element selector](/javascript-sdk/automation/touch-interactions#targeting-elements)     |
| target.duration?    | `number`                                                                                       | Duration of the tap                                                                         |
| options.timeout?    | `number`                                                                                       | If an element is provided, the amount of time to wait for it to appear in ms (defaults 10s) |
| options.matchIndex? | `number`                                                                                       | If multiple elements match the element selector, select the nth one                         |

### swipe(target, options)

Swipes on the screen at the given position, coordinate or element

```typescript
await session.swipe({ position: { x: '50%', y: '50%' }, gesture: 'up' })
await session.swipe({ coordinates: { x: 100, y: 100 }, gesture: 'up' })
await session.swipe({ element: { attributes: { text: 'OK' } }, gesture: 'up' })
```

|                     |                                                                                                |                                                                                                             |
| ------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| target.coordinates? | <p><code>{</code><br><code>x: number</code><br><code>y: number</code></p><p><code>}</code></p> | The coordinates in dip units to start the swipe                                                             |
| target.position?    | <p><code>{</code><br><code>x: string</code><br><code>y: string</code><br><code>}</code></p>    | The position on screen in %                                                                                 |
| target.element?     | `ElementSelector`                                                                              | An [element selector](/javascript-sdk/automation/touch-interactions#targeting-elements)                     |
| target.duration?    | `number`                                                                                       | Duration of the swipe                                                                                       |
| target.gesture      | `string \| function`                                                                           | The gesture of the swipe. See [swipe](/javascript-sdk/automation/touch-interactions#swipe) for more details |
| options.timeout?    | `number`                                                                                       | If an element is provided, the amount of time to wait for it to appear in ms (defaults 10s)                 |
| options.matchIndex? | `number`                                                                                       | If multiple elements match the element selector, select the nth one                                         |

### type(text)

Types the given text on the device

```typescript
await session.type("hello")
```

**Parameters**

| Name | Type     | Description  |
| ---- | -------- | ------------ |
| text | `string` | Text to type |

{% hint style="warning" %}
Typing is limited to 1000 characters at a time to ensure optimal performance and prevent potential disruptions. For larger payloads, you can use multiple 'type' operations.
{% endhint %}

{% hint style="info" icon="lightbulb-exclamation-on" %}
**Tip:** Need to delete a character after typing? Use `keypress`:

```js
await session.keypress('Backspace')
```

{% endhint %}

### keypress(character, options)

Sends a single key press to the device

```typescript
await session.keypress("a")
```

**Parameters**

| Name           | Type      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| -------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| key            | `string`  | <p>Key to send to the device ('a', 'b', etc.)</p><p>Also accepts text-editing <strong>keys:</strong> <code>Backspace</code><br><br>And hardware keys:<br><code>HOME</code><br><strong>Android Only:</strong><br><code>VOLUME\_UP</code><br><code>VOLUME\_DOWN</code><br><code>ANDROID\_KEYCODE\_MENU</code><br><code>LOCK\_SCREEN</code><br><code>UNLOCK\_SCREEN</code><br><strong>iOS Only:</strong><br><code>TOGGLE\_SCREEN\_LOCK</code></p> |
| options.shift? | `boolean` |                                                                                                                                                                                                                                                                                                                                                                                                                                                |

### setAppearance(appearance)

(iOS 13+ and Android 10+) Sets dark or light mode UI.

```typescript
await session.setAppearance("dark")
```

**Parameters**

<table><thead><tr><th>Name</th><th width="236">Type</th></tr></thead><tbody><tr><td>appearance</td><td><code>"dark" | "light"</code></td></tr></tbody></table>

### setLanguage(language)

Changes the current language and restarts the app

```typescript
await session.setLanguage("fr")
```

{% hint style="warning" %}
If your app does not automatically handle language/locale changes, you would need to explicitly call [restartApp](#restartapp) for this to take effect.\
\
Some apps might also cache data in the previously used language. In these cases use [reinstallApp](#reinstallapp) to clear any previous cached data.
{% endhint %}

**Parameters**

| Name     | Type     | Description   |
| -------- | -------- | ------------- |
| language | `string` | Language code |

### setLocation(lat, long)

Sets the simulated location of the device.

```typescript
await setLocation(-33.924434, 18.418391)
```

#### Parameters

| Name      | Type     | Description                                                                                                                                                                                                          |
| --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| latitude  | `number` | Decimal number between -90 and 90, representing the degrees of north or south of the Equator. Negative numbers indicate south of the Equator, and positive numbers indicate north of the Equator.                    |
| longitude | `number` | Decimal number between -180 and 180, representing the degrees of east or west of the Prime Meridian. Negative numbers indicate west of the Prime Meridian, and positive numbers indicate east of the Prime Meridian. |

### openUrl(url)

Opens a deep-link or web URL.&#x20;

{% hint style="info" %}
On iOS, the URL is limited to 2048 characters.
{% endhint %}

```typescript
await session.openUrl("https://appetize.io")
```

**Parameters**

| Name  | Type     | Description |
| ----- | -------- | ----------- |
| `url` | `string` | The URL     |

### shake()

Shakes device

```typescript
await session.shake()
```

### toggleSoftKeyboard()

Toggles the soft keyboard (iOS only)

```typescript
await session.toggleSoftKeyboard()
```

### biometryEnrollment(isEnrolled)

Sets the biometry enrollment status (*iOS Only*)

```typescript
await session.biometryEnrollment(true/false)
```

### biometry(match)

Simulate a matching fingerprint (Android 8+ only) or Face ID (iOS)

<pre class="language-typescript"><code class="lang-typescript">await session.biometry({
<strong>    match: true/false
</strong>})
</code></pre>

### allowInteractions(enabled)

Enables or disables all interactions on the device. Default is true.

```typescript
await session.allowInteractions(true/false)
```

### adbShellCommand(command) <a href="#adbshellcommand" id="adbshellcommand"></a>

Executes an `adb shell` command on the device (Android only)

```typescript
await session.adbShellCommand("am start -a android.intent.action.VIEW -d https://appetize.io/")
```

### launchApp(appId)

Launches the specified application using the provided `appId`.

{% hint style="info" %}
If the app is already running, it will be brought to the foreground instead of being relaunched. If the app was originally launched with params or a launchUrl, these will also be passed with this method.
{% endhint %}

```typescript
await session.launchApp(appId)
```

**Parameters**

| Name    | Type     | Description                                                                                                                                                                                                                                                                                                                |
| ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appId` | `string` | <p><strong>Android:</strong><br>The app's package name / appId (e.g., <code>com.example.app</code>) or <code>packageName/activityName</code>. If no activity name is specified, it defaults to the main launch activity.<br><strong>iOS:</strong><br>The app's bundle identifier (e.g., <code>com.example.app</code>).</p> |

### restartApp()

Restarts the app

```typescript
await session.restartApp()
```

### reinstallApp()

Reinstalls the app

```typescript
await session.reinstallApp()
```

### getUI()

Returns the UI as an XML string

```typescript
await session.getUI()
```

{% hint style="warning" %}
**Experimental**\
The data structure of the response is subject to change
{% endhint %}

{% tabs %}
{% tab title="Old Engine" %}
Returns an array of elements, with app and system content separated into two sections.

```json
[
  {
    "type": "app",
    "appId": {running app},
    "children": [ ... ] // Your app content
  },
  {
    "type": "app",
    "appId": "com.apple.springboard",
    "children": [ ... ] // Springboard (system UI) content
  }
]
```

{% endtab %}

{% tab title="New Engine" %}
A single combined hierarchy is returned, representing what’s actually visible to the user.

```json
[
  {
    "type": "app",
    "appId": {possible appId},
    "children": [ ... ] // All visible content on screen (e.g. Springboard/Navigation etc. related content too)
  }
]
```

{% endtab %}
{% endtabs %}

### addMedia(file)

{% hint style="info" %}
The maximum file size for uploading media is **50 MB**.
{% endhint %}

Upload media to the device.

```typescript
await session.addMedia(file)
```

Platform specific format support applies. See the [Media - Supported File Types documentation](/features/media#supported-files) for supported file types on Android and iOS.

### findElement(selector)

Returns an element that matches the selector. See [Targeting Elements](/javascript-sdk/automation/touch-interactions#targeting-elements).

*This is useful for waiting until an element appears.*

```javascript
const element = await session.findElement({
    attributes: {
        text: "Home"
    }
})
```

If multiple elements are found it will return the first element.

### findElements(selector)

Returns an array of all elements matching that selector. See [Targeting Elements](/javascript-sdk/automation/touch-interactions#targeting-elements).

```javascript
const elements = await session.findElements({
    attributes: { 
       accessibilityLabel: 'Like post'
    }
})
```

### playAction(action, options)

Play an automation Action or array of Actions.

```typescript
await session.playAction(action)
```

**Parameters**

| Name             | Type                  | Description                                                          |
| ---------------- | --------------------- | -------------------------------------------------------------------- |
| action           | `Record<string, any>` | Actions emitted from the [`session.on('action')`](#on-1) event       |
| options.timeout? | `number`              | Amount of time in ms to wait for the action to succeed (default 10s) |

### playAction**s(actions, options)**

Plays an array of actions.

```typescript
await session.playActions(actions)
```

**Parameters**

| Name             | Type                         | Description                                                         |
| ---------------- | ---------------------------- | ------------------------------------------------------------------- |
| actions          | `Array<Record<string, any>>` | Actions emitted from the [`session.on('action')`](#on-1) event      |
| options.timeout? | `number`                     | Amount of time in ms to wait for an action to succeed (default 10s) |

### waitForAnimations(options)

Waits until the there are no ongoing animations on the screen by waiting for the image to stabilize for at least 1 second.

```typescript
await session.waitForAnimations(options)
```

**Parameters**

| Name                    | Type     | Description                                                                                                                                         |
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| options.imageThreshold? | `number` | <p>The threshold for the amount of pixels (in %) that can change between frames before the image is considered to be stable.<br>(default 0.001)</p> |
| options.timeout?        | `number` | <p>The maximum amount of time (in ms) to wait for the image to stabilize.<br>(default 10s)</p>                                                      |

### waitForEvent(event, options) <a href="#waitforevent" id="waitforevent"></a>

Waits for an event to occur

```typescript
const networkEvent = await session.waitForEvent('network')

const requestEvent = await session.waitForEvent('network', event => {
    // resolves only when this condition is met
    return event.type === 'request'
})
```

**Parameters**

| Name               | Type                   | Description                                                                                          |
| ------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------- |
| event              | `string`               | One of the session [events](#on-session-1).                                                          |
| options.timeout?   | `number \| null`       | The maximum time (in milliseconds) to wait for the event to be emitted.                              |
| options.predicate? | `(data: T) => boolean` | The predicate condition to be satisfied, otherwise the function will continue to wait for the event. |

### waitForTimeout(timeout) <a href="#waitfortimeout" id="waitfortimeout"></a>

Waits for the given time to elapse (in ms)

```typescript
await session.waitForTimeout(5000)
```

**Parameters**

| Name    | Type     | Description              |
| ------- | -------- | ------------------------ |
| timeout | `number` | Timeout in milliseconds. |

### waitUntilReady()

Waits until the session is fully initialised and ready for use.

## Properties

### adbConnection

Info for connecting to the Android devices via adb. Requires [enableAdb](/javascript-sdk/configuration#enableadb) to be true.

See [AdbConnectionInfo](/javascript-sdk/api-reference/types/adbconnectioninfo).

### app

The Appetize app for the session, if applicable.

See [AppetizeApp](/javascript-sdk/api-reference/types/appetizeapp).

### config

The [config](/javascript-sdk/configuration) applied to the current session.

See [SessionConfig](/javascript-sdk/api-reference/types/sessionconfig).

### device

The current device. See [DeviceInfo](/javascript-sdk/api-reference/types/deviceinfo).

### networkInspectorUrl

The URL to chrome dev tools to inspect network logs. Requires [proxy](/javascript-sdk/configuration#proxy) to be set to `intercept`.

### path

The URL to the Appetizer server.

### token

The token of the Appetize session.


# Types


# AdbConnectionInfo

| Property | Type                                         |
| -------- | -------------------------------------------- |
| command  | string                                       |
| forwards | Array<{ destination: string; port: number }> |
| hash     | string                                       |
| hostname | string                                       |
| port     | number                                       |
| user     | string                                       |


# AppetizeApp

| Property        | Type                                                              |
| --------------- | ----------------------------------------------------------------- |
| buildId         | string                                                            |
| name?           | string                                                            |
| appDisplayName? | string                                                            |
| appVersionCode? | string                                                            |
| bundle?         | string                                                            |
| platform?       | string                                                            |
| versionCode?    | string                                                            |
| architectures?  | string                                                            |
| iconUrl?        | string                                                            |
| created?        | string                                                            |
| apps?[^1]       | [AppetizeApp](/javascript-sdk/api-reference/types/appetizeapp)\[] |

[^1]: if this is an app group, this will contain all the apps in the group.


# AndroidElementAttributes

| Property        | Type                                                    |
| --------------- | ------------------------------------------------------- |
| 'resource-id'?  | string \| null                                          |
| 'content-desc'? | string \| null                                          |
| class?          | string                                                  |
| ...             | Record\<string, string \| boolean \| undefined \| null> |


# Coordinates

| Property | Type   |
| -------- | ------ |
| x        | number |
| y        | number |


# DeviceInfo

| Property    | Type                                                        |
| ----------- | ----------------------------------------------------------- |
| type        | string                                                      |
| name        | string                                                      |
| osVersion   | string                                                      |
| orientation | 'portrait' \| 'landscape'                                   |
| screen      | { width: number; height: number; devicePixelRatio: number } |


# Element

| Property               | Type                                                                                                                                                                         |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| path                   | string                                                                                                                                                                       |
| type                   | string                                                                                                                                                                       |
| source                 | string                                                                                                                                                                       |
| bounds                 | [ElementBounds](/javascript-sdk/api-reference/types/elementbounds)                                                                                                           |
| attributes             | [IOSElementAttributes](/javascript-sdk/api-reference/types/ioselementattributes) \| [AndroidElementAttributes](/javascript-sdk/api-reference/types/androidelementattributes) |
| accessibilityElements? | [IOSAccessibilityElement](/javascript-sdk/api-reference/types/iosaccessibilityelement)\[]                                                                                    |


# ElementBounds

| Property | Type   |
| -------- | ------ |
| x        | number |
| y        | number |
| width    | number |
| height   | number |


# IOSAccessibilityElement

| Property                 | Type                                                               |
| ------------------------ | ------------------------------------------------------------------ |
| accessibilityLabel?      | string                                                             |
| accessibilityIdentifier? | string                                                             |
| accessibilityValue?      | string                                                             |
| accessibilityHint?       | string                                                             |
| accessibilityFrame?      | [ElementBounds](/javascript-sdk/api-reference/types/elementbounds) |
| accessibilityTraits?     | number                                                             |


# IOSElementAttributes

| Property                 | Type                                                    |
| ------------------------ | ------------------------------------------------------- |
| accessibilityLabel?      | string                                                  |
| accessibilityIdentifier? | string                                                  |
| accessibilityValue?      | string                                                  |
| accessibilityHint?       | string                                                  |
| text?                    | string                                                  |
| ~~class?~~               | string                                                  |
| ~~baseClass?~~           | string                                                  |
| title?                   | string                                                  |
| label?                   | string                                                  |
| placeholder?             | string                                                  |
| userInteractionEnabled?  | boolean                                                 |
| ~~isHidden?~~            | boolean                                                 |
| ...                      | Record\<string, string \| boolean \| undefined \| null> |


# NetworkRequest

| Property        | Type                                                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type            | 'request'                                                                                                                                               |
| serverIPAddress | string                                                                                                                                                  |
| requestId       | string                                                                                                                                                  |
| request         | { method: string; url: string; httpVersion: string; cookies: string\[]; headers: Array; queryString: string\[]; headersSize: number; bodySize: number } |
| cache           | Record\<string, any>                                                                                                                                    |


# NetworkResponse

| Property        | Type                                                                                                                                                                                                                                                                                      |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type            | 'response'                                                                                                                                                                                                                                                                                |
| serverIPAddress | string                                                                                                                                                                                                                                                                                    |
| requestId       | string                                                                                                                                                                                                                                                                                    |
| request         | { method: string; url: string; httpVersion: string; cookies: string\[]; headers: Array; queryString: string\[]; headersSize: number; bodySize: number }                                                                                                                                   |
| response        | { status: number; statusText: string; httpVersion: string; cookies: string\[]; headers: Array; redirectURL: string; headersSize: number; bodySize: number; content: { size: number; mimeType: string; compression: number; text: string }; postData: { mimeType: string; text: string } } |
| cache           | Record\<string, any>                                                                                                                                                                                                                                                                      |


# SessionConfig

See [Configuration](/javascript-sdk/configuration) for more information.

| Property                 | Type                     |
| ------------------------ | ------------------------ |
| buildId?                 | string                   |
| device?                  | string                   |
| osVersion?               | string                   |
| scale?                   | number \| 'auto'         |
| autoplay?                | boolean                  |
| adbShellCommand?         | string                   |
| androidPackageManager?   | boolean                  |
| appearance?              | string                   |
| audio?                   | boolean                  |
| codec?                   | string                   |
| debug?                   | boolean                  |
| deviceColor?             | string                   |
| disableVirtualKeyboard?  | boolean                  |
| enableAdb?               | boolean                  |
| grantPermissions?        | boolean                  |
| hidePasswords?           | boolean                  |
| iosKeyboard?             | string                   |
| iosAutocorrect?          | string                   |
| language?                | string                   |
| launchUrl?               | string                   |
| launchArgs?              | Array\<string \| number> |
| locale?                  | string                   |
| location?                | number\[]                |
| noVideo?                 | boolean                  |
| orientation?             | string                   |
| platform?                | 'ios' \| 'android'       |
| params?                  | Object                   |
| plistEdit?               | Object                   |
| proxy?                   | string                   |
| record?                  | boolean                  |
| region?                  | string                   |
| screenOnly?              | boolean                  |
| showRotateButtons?       | boolean                  |
| timezone?                | string                   |
| endSessionRedirectUrl?   | string                   |
| userInteractionDisabled? | boolean                  |
| volume?                  | number                   |


# SwipeMove

| Property | Type   |
| -------- | ------ |
| x        | number |
| y        | number |
| t        | number |


# RecordedAction

| Union                                                                                |
| ------------------------------------------------------------------------------------ |
| [RecordedTapAction](/javascript-sdk/api-reference/types/recordedtapaction)           |
| [RecordedSwipeAction](/javascript-sdk/api-reference/types/recordedswipeaction)       |
| [RecordedKeypressAction](/javascript-sdk/api-reference/types/recordedkeypressaction) |


# RecordedSwipeAction

| Property       | Type                                                                     |
| -------------- | ------------------------------------------------------------------------ |
| type           | string                                                                   |
| id?            | string                                                                   |
| appId?         | string                                                                   |
| time           | number                                                                   |
| appId          | string                                                                   |
| element?       | [Element](/javascript-sdk/api-reference/types/element)                   |
| coordinates    | [Coordinates](/javascript-sdk/api-reference/types/coordinates)           |
| position       | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |
| localPosition? | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |
| moves          | [SwipeMove](/javascript-sdk/api-reference/types/swipemove)\[]            |


# RecordedKeypressAction

| Property   | Type    |
| ---------- | ------- |
| type       | string  |
| id?        | string  |
| appId?     | string  |
| key        | string  |
| shiftKey   | boolean |
| character? | string  |


# RecordedPosition

| Property | Type   |
| -------- | ------ |
| x        | number |
| y        | number |


# RecordedTapAction

| Property       | Type                                                                     |
| -------------- | ------------------------------------------------------------------------ |
| type           | string                                                                   |
| id?            | string                                                                   |
| appId?         | string                                                                   |
| time           | number                                                                   |
| appId          | string                                                                   |
| element?       | [Element](/javascript-sdk/api-reference/types/element)                   |
| coordinates    | [Coordinates](/javascript-sdk/api-reference/types/coordinates)           |
| position       | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |
| localPosition? | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |
| duration?      | number                                                                   |


# RecordedTouchAction

| Property       | Type                                                                     |
| -------------- | ------------------------------------------------------------------------ |
| type           | string                                                                   |
| id?            | string                                                                   |
| appId?         | string                                                                   |
| time           | number                                                                   |
| appId          | string                                                                   |
| element?       | [Element](/javascript-sdk/api-reference/types/element)                   |
| coordinates    | [Coordinates](/javascript-sdk/api-reference/types/coordinates)           |
| position       | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |
| localPosition? | [RecordedPosition](/javascript-sdk/api-reference/types/recordedposition) |


# UserInteraction

| Property  | Type    |
| --------- | ------- |
| timeStamp | number  |
| type      | string  |
| altKey?   | boolean |
| shiftKey? | boolean |
| xPos?     | number  |
| yPos?     | number  |


# Testing

Reliably End-to-End (E2E) test your Android and iOS mobile apps with Appetize AppRecorder for Playwright

## What is Appetize Automations for Playwright?

Appetize Automations for [Playwright](https://playwright.dev/) is an integration that brings the power of Appetize's Mobile UI automation framework to Playwright. It allows you to easily build, run and debug tests on Appetize devices while benefitting from the many tools that Playwright offers.

<figure><img src="https://lh7-us.googleusercontent.com/slidesz/AGV_vUfDhei3zJ-IQe4MT8ejIwMKhcCz1LBXabFRMf85w47QU9zznRFGq11pWA0mQrCRhmp0l4qLEYjx6GziqEdkqsIEjpBh0sREzSnsVe6fEx3hiJp2PKxh7PmDvj7pU__eOGzkzzuPb_C9r-hXQHpYNW2-v-sBXyF5=nw?key=Ov-qIhkbe_J50OTU5jQN9g" alt=""><figcaption><p>Playwright with Appetize AppRecorder in action</p></figcaption></figure>

## **Why Should I Use Playwright with Appetize Automations?**

### **Platform-independent**

**Appetize Automations** is built to be platform-independent, supporting all major mobile app development frameworks (React Native, Flutter, KMP, Native, etc.)

### **Unified Testing Solution**

The Playwright integration with Appetize provides a unified testing solution familiar to web teams, enabling seamless testing across mobile, [mobile-web](/testing/web-tests-on-mobile-browsers), and web applications.

### **Ease of Use**

**Appetize Automations** is designed to be familiar and easy to use, featuring an inspector tool for debugging and understanding the app under test, along with a low-code automation tool to help you get started quickly.

#### Inspector Mode

<figure><img src="https://lh7-us.googleusercontent.com/slidesz/AGV_vUcsTEgw8qRe7gDZSXmjatB7OiejviWbp1elsBjdxnHl8qhx0BZ2i-2Y-DhkyzSo5WK7owLZ434lBuwjUDG-dTQsKzZsSmBlUtRjnnlbYQy7wRP_fa-x3ZsdE4L57z0CjyHrfDt6huXw1Wup90K0tu1FvfkPmLk=nw?key=Ov-qIhkbe_J50OTU5jQN9g" alt="" width="563"><figcaption><p>AppRecorder Inspector Mode</p></figcaption></figure>

#### **Low-Code Automation Tool (**&#x41;utomation Recorder)

<figure><img src="https://lh7-us.googleusercontent.com/slidesz/AGV_vUdeXA9PgE2hoG38KEV5OzNW10DnN8a81tzJ1WqSEj-XS3xcQxfKSXEd6QS5-J0dZn6HfsjNIdp2fiv3f0sPjWEqzB4fyqpRZUUDMouEEHNcws49zEVxunD_1S9h97kRRwvckFRDtqe0j1zR5aVufDKexzaSyINZ=nw?key=Ov-qIhkbe_J50OTU5jQN9g" alt="" width="563"><figcaption><p>Low-code automation example</p></figcaption></figure>

See [UI Automation](/features/ui-automation) for more information.

### **Consistent and Reliable Test Environments**

All tests are run in the Appetize environment, ensuring consistent behavior and results whether executed locally or through CI/CD pipelines.

### **Quick Test Execution**

Experience fast app launches (as quick as 4 seconds) and efficient test execution with built-in support for concurrency.

### Benefit from the many tools that Playwright offers

Leverage Playwright's powerful tools, including the [Trace Viewer](https://playwright.dev/docs/trace-viewer-intro), [CI configurations](https://playwright.dev/docs/ci), and the [VS Code extension](https://playwright.dev/docs/getting-started-vscode). See the [Playwright documentation](https://playwright.dev/docs/intro) for more information.

## Getting Started

Visit our Getting Started section to begin:

{% content-ref url="/pages/T9QPwZqPBuxqMgyfOMZL" %}
[Getting Started](/testing/getting-started)
{% endcontent-ref %}


# Getting Started

Getting Started with Appetize AppRecorder and Playwright

You can follow along with the installation steps below to start a new project.

## Installation

Get started by installing Playwright with Appetize using **npm:**

```sh
npm init @appetize/playwright@latest
```

Run the install command and select the following to get started:

* Your Appetize App's [**buildId**](#user-content-fn-1)[^1]
* The **preferred default device**

<figure><img src="https://lh7-us.googleusercontent.com/slidesz/AGV_vUdOgTMhY5_mOGlIO1bM1w1FNQjzN7R9tfbQ8ItN0DnHyeVeA-8b94lKG71uxzWDcOZwnDf5tpGP8TpfS_lGwVgNACY3eXtKx4Ku0XhW61hoMbQXc8QvUmoexW21LJRtwCcNguKCcfeZgoqc9XLOIzYfQTYWzKox=nw?key=Ov-qIhkbe_J50OTU5jQN9g" alt="" width="563"><figcaption></figcaption></figure>

## What's Installed <a href="#whats-installed" id="whats-installed"></a>

* A Playwright project will be created (see [Playwright documentation](https://playwright.dev/docs/intro#installing-playwright)).
* The `@appetize/playwright` npm package will be installed.
* The `playwright.config.ts` file will be configured for Appetize with the specified values for the default device and app.

{% hint style="info" %}
See [Test Configuration](/testing/test-configuration) for more advanced configurations.
{% endhint %}

* An example test file, `app.spec.ts`, will be added.

## Usage

Update the `app.spec.ts` file in your tests folder to include a test relevant to your application

```javascript
import { test, expect } from '@appetize/playwright'

test('example test', async ({ session }) => {
    await expect(session).toHaveElement({
         attributes: {
          // replace with the text of an element that appears on your app
            text: 'Hello world' 
        }
    })
})
```

Once you've updated the test file for your app, run the test with:

```bash
npx playwright test --headed

# or, headlessly

npx playwright test
```

## Next Steps

{% content-ref url="/pages/X4MFBApmCt8df6lU86N8" %}
[Writing Tests](/testing/writing-tests)
{% endcontent-ref %}

{% content-ref url="/pages/3e8Dq2Byt8xkm8Hi1H6k" %}
[Test Configuration](/testing/test-configuration)
{% endcontent-ref %}

[^1]: previously known as **publicKey**


# Writing Tests

Each test will have an [Appetize session](/javascript-sdk/api-reference#session-1) for you to interact with your device (like a [page](https://playwright.dev/docs/pages) in Playwright). You can then assert the app based on some criteria, such as the existence of a UI element, a network request, or do a screenshot test.

## Your first test

Take a look at the following test example:

```javascript
import { test, expect } from '@appetize/playwright'

// reinstall app after each test to reset data
test.afterEach(async ({ session }) => {
    await session.reinstallApp()
})

test('logs in to the app', async ({ session }) => {
    // type username
    await session.tap({ 
        element: { 
            attributes: {
                accessibilityIdentifier: 'username_field' 
            }
        } 
    })
    await session.type('jordan')
    
    // type password
    await session.tap({ 
        element: { 
            attributes: {
                accessibilityIdentifier: 'password_field' 
            }
        } 
    })
    await session.type('secretpassword')
    
    // tap login button
    await session.tap({ 
        element: { 
            attributes: {
                text: 'Login' 
            }
        } 
    })
    
    // assert that an element with 'Hello Jordan' exists on the screen
    await expect(session).toHaveElement({ 
        attributes: {
            text: 'Hello Jordan' 
        }
    })
})
```

This will load a hypothetical app with a login screen. It will tap on the username and password fields, type some credentials, log in, and assert that a UI element with text "Hello Jordan" exists.

## Actions

Each test provides the `session` for your app. You can use [Automation](/javascript-sdk/automation) actions here to interact with it as you need.

```javascript
test('scrolls through the news feed', async ({ session }) => {
    await session.swipe({
        position: {
            x: '50%',
            y: '50%',
        },
        gesture: 'up'
    })
})
```

### Sequencing actions

All actions are promises that will await until the interaction has been played on the device. If your action [targets an element](/javascript-sdk/automation/touch-interactions#targeting-elements), Appetize will wait for the element to appear before proceeding, which is helpful when your action results in a change in UI.

```javascript
test('navigates to a settings menu', async ({ session }) => {
    await session.tap({ 
        element: { 
            attributes: {
                text: 'Settings' 
            }
        } 
    })
    
    await session.tap({ 
        element: { 
            attributes: {
                text: 'Notifications' 
            }
        } 
    }) 
})
```

All actions that target an element will wait up to 10 seconds for an element to appear. If you need to wait for an element to appear without interacting on it, you can use [waitForElement()](#waitforelement)

```javascript
test('screenshot of the settings page', async ({ session }) => {
    // tapping on 'Settings' will trigger a screen transition
    await session.tap({ element: { text: 'Settings' } })
    
    // wait for a UI element to appear
    await session.waitForElement({ text: 'Notifications' })
    
    // take a screenshot
    const screenshot = await session.screenshot()
    expect(screenshot.data).toMatchSnapshot()    
})
```

These timeouts are also configurable (see [Timeouts](/javascript-sdk/automation/touch-interactions#timeouts).)

## Assertions

### expect

Playwright uses the [expect](https://jestjs.io/docs/expect) library for assertions. We have added custom async matchers for asserting on application state.

#### toHaveElement

Asserts that an element exists in the current application UI

```javascript
await expect(session).toHaveElement({ 
    attributes: {
        text: 'Hello' 
    }
})
```

It can take additional options to change the behaviour of the assertion:

```javascript
await expect(session).toHaveElement(
    {
        attributes: {
            text: 'Hello',
        }
    },
    {        
        timeout: 10000, // time in ms to wait for element to appear (default 10000)
        matches: 1      // require this amount of elements to be found
    }
)
```

#### not.toHaveElement

Asserts that an element does **not** exists in the current application UI

```javascript
await expect(session).not.toHaveElement(
    {
        attributes: {
            text: 'Hello' 
        }
    },
    {
        // We recommend setting a lower timeout for elements we know will not appear, 
        // as it will spend this time looking for the element.
        timeout: 1000, // time in ms to wait for element to appear (default 10000)
    }
 )
```

### Screenshot comparisons

[toMatchSnapshot()](https://playwright.dev/docs/api/class-snapshotassertions#snapshot-assertions-to-match-snapshot-2) works with [session.screenshot()](/javascript-sdk/automation/device-commands#screenshot), allowing you to do screenshot comparisons of your app.

*Note: screenshot tests are fragile by nature as any change in the UI could cause it to fail. It is always better to assert on a narrow piece of application state, such as* [*toHaveElement*](#tohaveelement)*, or on network/debug log output.*

```javascript
test('loads the home tab', async ({ sesssion }) => {
    await session.findElement({ attributes: { text: 'Home' } })
    
    const screenshot = await session.screenshot()
    
    await expect(screenshot.data).toMatchSnapshot()
})
```

### Network

An example of how you can assert that a network request was made.

[See documentation for network events](/javascript-sdk/api-reference#on-session)

```javascript
test.use({
    config: {
         proxy: 'intercept',
     }
})

test('makes a network request to the API with authentication', async ({ session }) => {
    const { request } = await session.waitForEvent('network', event => {
        if (event.request.url.startsWith('https://api.example.com')) {
            return true;
        }
    })
    
    // request.headers is an array of objects
    expect(request.headers).toContainEqual({
        name: 'Authorization',
        value: expect.stringMatching(/^Basic /),
    })
})

```

## Helpers

`session` contains a few helper methods for writing tests that may come in handy

### waitForElement

Waits for an element to appear.

```javascript
await session.waitForElement({ attributes: { text: "Hello" } })

await session.waitForElement(
    { attributes: { text: "Hello" } }, 
    { 
        matches: 2, // wait for exactly 2 elements to match the selector
        timeout: 10000 // wait a maximum of 10 seconds (default)
    }
)
```

### waitForEvent

Waits for an event to occur

```javascript
const networkEvent = await session.waitForEvent('network')

const requestEvent = await session.waitForEvent('network', event => {
    // resolves only when this condition is met
    return event.type === 'request'
})
```

### waitForTimeout

Waits for the given time to elapse (in ms)

```javascript
await session.waitForTimeout(5000)
```

## Real life applications

The [Common Testing Scenarios](/guides-and-samples/common-testing-scenarios) page highlights typical challenges mobile developers face when writing tests and explains how to use key features covered on this page, such as waiting for network events, validating elements, and comparing screenshots.


# Running Tests

You can run your tests exactly how you would with a regular Playwright project:

```bash
npx playwright test

# or

npx playwright test --headed
```

[See Playwright documentation for running tests](https://playwright.dev/docs/running-tests)

## Parallel Tests

All tests in a test suite will run serially using the same session. This keeps you from re-entering the queue for each individual test and overall leads to faster test times. However, should a test fail, the session ends and a new one is requested.

You may run multiple test suites in parallel so long as your Appetize account has capacity for concurrent sessions. To do this, increase the [number of workers ](https://playwright.dev/docs/test-parallel#worker-processes)in `playwright.config.ts`.


# Test Configuration

Run your tests against multiple device configurations

## Changing configuration

You can change the configuration for a test suite with `test.use`. Note that config changes will start a new session when used within a `test.describe`.

See [Playwright documentation](https://playwright.dev/docs/test-use-options) for more details on `test.use`.

```javascript
import { test, expect } from '@appetize/playwright'

test.use({
  config: {
    publicKey: '<buildId|publicKey>'
    device: 'nexus5'
  },
});

test('app works on nexus5', async ({ session }) => {
  ...
})
```

## Getting configuration

The current configuration can be accessed with the `config` argument in the test

```javascript
test('my test', async ({ session, config }) => {
   if (config.osVersion === '7.0') {
      // do os 7.0 specific behaviour
   } else {
      
   }
})
```

You can also use this to skip tests

```javascript
test.describe('iOS 16 features', () => {
    // skip suite if osVersion is less than 16
    test.skip(({ config }) => parseInt(config.osVersion) < 16);
    
    test('some feature', async ({ session }) => { ... })
})
```

## Projects

[Projects](https://playwright.dev/docs/test-projects) allow you to run your entire test suite with different configurations. This is useful if you have a cross platform app or wish to test against a set of devices and/or osVersions.

Below are some examples that may fit your use case.

### Examples

#### Test Android and iOS apps

Runs tests under `tests/ios` for the iOS configuration, and `tests/android` for the Android configuration

```javascript
const config = {
    // ... 
    
    projects: [
        {
            name: 'ios',
            testDir: './tests/ios',
            use: {
                config: {
                    device: 'iphone14pro',
                    publicKey: '<IOS APP BUILD ID (PUBLIC KEY)>'
                }
            },
        },
        {
            name: 'android',
            testDir: './tests/android',
            use: {
                config: {
                    device: 'pixel6',
                    publicKey: '<ANDROID APP BUILD ID (PUBLIC KEY)>'
                }
            },
        }      
    ]
} 
   
```

#### Test iOS app against multiple iOS Versions

Runs the test suite against iOS 16 and iOS 15

```javascript
const config = {
    // ... 
    
    use: {
        config: {
            publicKey: '<BUILD ID (PUBLIC KEY)>'
        }
    },
    projects: [
        {
            name: 'ios-16',
            use: {
                config: {
                    device: 'iphone14pro',
                    osVersion: '16',
                }
            },
        },
        {
            name: 'ios-15',
            use: {
                config: {
                    device: 'iphone14pro',
                    osVersion: '15'
                }
            },
        }
    ]
} 
   
```

#### Test Android against multiple devices

Runs the test suite against a Pixel 7 and a Pixel 6

```javascript
const config = {
    // ... 
    
    use: {
        config: {
            publicKey: '<BUILD ID (PUBLIC KEY)>'
        }
    },
    projects: [
        {
            name: 'pixel7',
            use: {
                config: {
                    device: 'pixel7'
                }
            },
        },
        {
            name: 'pixel6',
            use: {
                config: {
                    device: 'pixel6'
                }
            },
        }
    ]
}
```

## During tests

You can reference the current project configuration in your tests. This is useful if you need to change or skip a test based on a certain device


# Continuous Integration

[See Playwright documentation for running on CI](https://playwright.dev/docs/ci)


# Record Tests (experimental)

{% hint style="info" %}
This is an experimental feature and may change or be removed in the future
{% endhint %}

Tests can be generated by recording your own interactions with the device through the browser.

Simply add `await session.record()` anywhere in your test and then run your test in headed mode (`npx playwright test --headed`)

```javascript
test('plays back my interactions', async ({ session }) => {
    await session.record()
})
```

Playwright will pause at `session.record()` and any interactions you make on the device will be put here.

Once you are finished, click the Resume button on the Playwright Inspector:

<figure><img src="https://github.com/appetizeio/appetize-docs-gitbook/blob/master/.gitbook/assets/playwright-inspector.png" alt=""><figcaption><p>Image of Playwright Inspector window</p></figcaption></figure>

You will see that `session.record()` has been replaced with your interactions:

```javascript
test('plays back my interactions', async ({ session }) => {
  // Recorded using session.record()
  // 1. click on element with class "android.widget.Button"
  // 2. click on element with class "android.widget.TextView"
  // 3. click on element with class "android.widget.Button"
  await session.playActions([
   ...
  ])
})
```

Run the test again and it will replay the recording.


# Trace Viewer

The [Playwright Trace Viewer](https://playwright.dev/docs/trace-viewer-intro) allows you to review the playback of your test. This can be recorded locally, or downloaded from CI when a test fails.

It is designed for web tests, so while the features like network logging and DOM inspection will not work for Appetize, you can still use it to look at the video recording of the test.

{% code title="playwright.config.ts" %}

```typescript
const config = {
  ...
  use: {
    trace: 'retain-on-failure'
  },
}
```

{% endcode %}


# Web Tests on Mobile Browsers

With  a simple import change and a configuration update, you can use your existing Playwright tests  to also do native browser testing with the Appetize integration

## Prerequisites

Before you begin, make sure you have the following:

* **Preferred Android Device:** You will need one of our [Android devices](/features/devices-and-os-versions) with Chrome 87 or higher (Android 13+ is recommended for best results).
* **ADB Installed:** Ensure [Android Debug Bridge (ADB)](https://developer.android.com/tools/adb) is installed on your system.
* **Enable ADB Tunnel:** Enable [ADB tunnel](https://docs.appetize.io/features/advanced-features/android/adb-tunnel#enable-adb-tunnel) on the session used.

## Configuration

If you haven’t set up Appetize with Playwright yet, we recommend look at our [**Getting Started**](/testing/getting-started) guide. After successfully integrating Appetize, open the `playwright.config.ts` file to configure the settings for your desired device for native browser testing. If you are already using Playwright for web testing, you can easily add a new project as shown below.

For the **buildId**, we suggest using your [device's sandbox](https://appetize.io/standalone) ID, which can be found in the URL bar. Look for the identifier that starts with `standalone_***` and use that as your buildId. Here’s an example configuration:

```typoscript
{
    name: 'Appetize Native Browser Test',
    use: {
        config: {
                device: 'pixel7',
                osVersion: '13',
                publicKey: 'standalone_***',
                enableAdb: true
        }
    }
}
```

## Page Fixture

The Appetize integration overrides the default page fixture to automatically determine when to use a standard browser (e.g., Chrome or Firefox) versus a native device browser for testing. This means all the logic is handled for you, simplifying the testing process.

### Update your imports

To get started, simply update your imports. Change your Playwright import for `test` to the Appetize version:

From:

```typescript
import { test } from '@playwright/test';
```

To:

```typescript
import { test } from '@appetize/playwright';
```

### Run your tests

{% content-ref url="/pages/h42WUKs7MlwRPNgN43kR" %}
[Running Tests](/testing/running-tests)
{% endcontent-ref %}


# REST API

Seamlessly integrate Appetize into your CI/CD pipeline by making use of our REST API.

## 🚀 Getting Started

{% hint style="success" %}
**Private Instance Users**\
If you're using a **Private Enterprise Instance**, update the domain for your requests:

* For **v1 APIs**, use: `https://custom.appetize.io`
* For **v2 APIs**, use: `https://custom.appetize.io/api/`
  {% endhint %}

### 1. Get Your API Token

Log in to the Appetize dashboard and navigate to [**Organization → API Token**](https://appetize.io/organization/api-token)

For full instructions on how to generate, manage and revoke tokens, see the [API Tokens guide](/account/api-tokens).

{% hint style="info" %}
You must be an organization admin to create or view tokens.
{% endhint %}

### 2.  Authenticate Your Requests

Use the `X-API-KEY` header to authenticate every API request.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X GET https://api.appetize.io/v1/apps \
  -H "X-API-KEY: your_api_token"
```

{% endtab %}

{% tab title="JavaScript " %}

```typescript
fetch("https://api.appetize.io/v1/apps", {
  headers: {
    "X-API-KEY": "your_api_token"
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
```

{% endtab %}
{% endtabs %}

### 3. Explore our API

You’re all set to start using the API.

Each endpoint includes:

* 📦 Sample requests and responses
* 🌐 Code samples in multiple languages (like cURL, Python, JavaScript)

Use these to quickly understand how each endpoint works and integrate it into your own tools.

## Format & Conventions

* **POST** requests must include a `Content-Type: application/json` header.
* All responses are in **JSON** format.

## Response Codes

| Status code            | Details                                               |
| ---------------------- | ----------------------------------------------------- |
| **200**                | OK - Everything worked as expected.                   |
| **400**                | Bad Request - Often missing a required parameter.     |
| **401**                | Unauthorized - No valid API token provided.           |
| **404**                | Not Found - No app found for **publicKey** specified. |
| **500, 502, 503, 504** | Server error - something went wrong on our server.    |


# API Tokens

## List API Tokens

> Returns a paginated list of API tokens with obfuscated token IDs.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"ApiTokenCollection":{"type":"object","properties":{"tokens":{"type":"array","items":{"$ref":"#/components/schemas/ApiToken"}}}},"ApiToken":{"type":"object","required":["id","obfuscatedToken","label","role","created","createdBy"],"properties":{"id":{"type":"string","description":"The ID of the API token."},"obfuscatedToken":{"type":"string","description":"The string representing the token obfuscated"},"label":{"type":"string","description":"The label of the API token."},"role":{"$ref":"#/components/schemas/ApiTokenRole"},"lastUsed":{"type":"string","nullable":true,"description":"The date and time the API token was last used."},"created":{"type":"string","description":"The date and time the API token was last used."},"createdBy":{"type":"string","description":"The email of the user who created the API token."}}},"ApiTokenRole":{"type":"string","description":"The role of the API token.","enum":["developer","admin","systemAdmin","systemAnalyst"]},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/api-tokens":{"get":{"summary":"List API Tokens","tags":["API Tokens"],"description":"Returns a paginated list of API tokens with obfuscated token IDs.","operationId":"listApiTokens","parameters":[{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of results to return per page","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/ApiTokenCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Create a new API token

> Creates a new API token. Returns the full token only once. Token must be saved securely by the user.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"CreateApiTokenRequest":{"type":"object","required":["label","role"],"properties":{"label":{"type":"string","description":"The label of the API token."},"role":{"$ref":"#/components/schemas/ApiTokenRole"}}},"ApiTokenRole":{"type":"string","description":"The role of the API token.","enum":["developer","admin","systemAdmin","systemAnalyst"]},"ApiTokenWithSecret":{"type":"object","required":["id","obfuscatedToken","token","label","role","created","createdBy"],"properties":{"id":{"type":"string","description":"The ID of the API token."},"obfuscatedToken":{"type":"string","description":"The string representing the token obfuscated"},"token":{"type":"string","description":"The full token string"},"accountId":{"type":"string","description":"The ID of the account."},"label":{"type":"string","description":"The label of the API token."},"createdBy":{"type":"string","description":"The email of the user who created the API token."},"created":{"type":"string","description":"The date and time the API token was created."},"lastUsed":{"type":"string","nullable":true,"description":"The date and time the API token was last used."},"role":{"$ref":"#/components/schemas/ApiTokenRole"}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/api-tokens":{"post":{"summary":"Create a new API token","tags":["API Tokens"],"description":"Creates a new API token. Returns the full token only once. Token must be saved securely by the user.","operationId":"createApiToken","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiTokenRequest"}}}},"responses":{"201":{"description":"API Token created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiTokenWithSecret"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get an API token by id

> Retrieve a specific API token’s info.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"ApiToken":{"type":"object","required":["id","obfuscatedToken","label","role","created","createdBy"],"properties":{"id":{"type":"string","description":"The ID of the API token."},"obfuscatedToken":{"type":"string","description":"The string representing the token obfuscated"},"label":{"type":"string","description":"The label of the API token."},"role":{"$ref":"#/components/schemas/ApiTokenRole"},"lastUsed":{"type":"string","nullable":true,"description":"The date and time the API token was last used."},"created":{"type":"string","description":"The date and time the API token was last used."},"createdBy":{"type":"string","description":"The email of the user who created the API token."}}},"ApiTokenRole":{"type":"string","description":"The role of the API token.","enum":["developer","admin","systemAdmin","systemAnalyst"]},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/api-tokens/{id}":{"get":{"summary":"Get an API token by id","tags":["API Tokens"],"description":"Retrieve a specific API token’s info.","operationId":"getApiToken","parameters":[{"in":"path","name":"id","description":"The id for the API token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The API token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiToken"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"API Token not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Rekove an API token

> Revoke an API token.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/api-tokens/{id}":{"delete":{"summary":"Rekove an API token","tags":["API Tokens"],"description":"Revoke an API token.","operationId":"rekoveApiToken","parameters":[{"in":"path","name":"id","description":"The id for the API token to revoke","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"API Token rekoved"},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"API Token not found","content":{"application/json":{}}}}}}}}
```


# App Groups

## List App Groups

> List all app groups in the account, with associated metadata.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"AppGroupCollection":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/AppGroup"}}}},"AppGroup":{"type":"object","required":["id","platform","created","createdBy","apps","resolvedBuilds"],"properties":{"id":{"type":"string","description":"The unique identifier for the App Group."},"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app-group was created.","type":"string","format":"date-time"},"updated":{"description":"The date the app-group was last updated.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app group.","type":"string"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app group was last played.","type":"string","format":"date-time"},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}},"resolvedBuilds":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedBuild"}}}},"BuildQuery":{"type":"object","minProperties":1,"properties":{"buildId":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app."},"versionName":{"type":"string","description":"The version name, as specified by the app. In Android this is the versionName, in iOS this is the CFBundleShortVersionString."},"buildNumber":{"type":"string","description":"The build number, as specified by the app. In Android this is the versionCode, in iOS this is the CFBundleVersion."},"tags":{"type":"array","default":[],"items":{"type":"string"}}}},"ResolvedBuild":{"type":"object","nullable":true,"allOf":[{"$ref":"#/components/schemas/Build"}]},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/app-groups":{"get":{"summary":"List App Groups","tags":["App Groups"],"description":"List all app groups in the account, with associated metadata.","operationId":"listAppGroups","parameters":[{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of results to return per page","schema":{"type":"integer"}},{"in":"query","name":"search","description":"The search term to filter by","schema":{"type":"string"}},{"in":"query","name":"platform","description":"The platform to filter by","schema":{"type":"string","enum":["ios","android"]}},{"in":"query","name":"created","description":"The created date to filter by","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"updated","description":"The updated date to filter by,","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/AppGroupCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Create App Group

> Create a new app group.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"UploadAppGroup":{"type":"object","required":["platform","apps"],"properties":{"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}}}},"BuildQuery":{"type":"object","minProperties":1,"properties":{"buildId":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app."},"versionName":{"type":"string","description":"The version name, as specified by the app. In Android this is the versionName, in iOS this is the CFBundleShortVersionString."},"buildNumber":{"type":"string","description":"The build number, as specified by the app. In Android this is the versionCode, in iOS this is the CFBundleVersion."},"tags":{"type":"array","default":[],"items":{"type":"string"}}}},"AppGroup":{"type":"object","required":["id","platform","created","createdBy","apps","resolvedBuilds"],"properties":{"id":{"type":"string","description":"The unique identifier for the App Group."},"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app-group was created.","type":"string","format":"date-time"},"updated":{"description":"The date the app-group was last updated.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app group.","type":"string"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app group was last played.","type":"string","format":"date-time"},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}},"resolvedBuilds":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedBuild"}}}},"ResolvedBuild":{"type":"object","nullable":true,"allOf":[{"$ref":"#/components/schemas/Build"}]},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/app-groups":{"post":{"summary":"Create App Group","tags":["App Groups"],"description":"Create a new app group.","operationId":"createAppGroup","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAppGroup"}}}},"responses":{"201":{"description":"App Group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppGroup"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get App Group

> Get an app group by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"AppGroup":{"type":"object","required":["id","platform","created","createdBy","apps","resolvedBuilds"],"properties":{"id":{"type":"string","description":"The unique identifier for the App Group."},"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app-group was created.","type":"string","format":"date-time"},"updated":{"description":"The date the app-group was last updated.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app group.","type":"string"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app group was last played.","type":"string","format":"date-time"},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}},"resolvedBuilds":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedBuild"}}}},"BuildQuery":{"type":"object","minProperties":1,"properties":{"buildId":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app."},"versionName":{"type":"string","description":"The version name, as specified by the app. In Android this is the versionName, in iOS this is the CFBundleShortVersionString."},"buildNumber":{"type":"string","description":"The build number, as specified by the app. In Android this is the versionCode, in iOS this is the CFBundleVersion."},"tags":{"type":"array","default":[],"items":{"type":"string"}}}},"ResolvedBuild":{"type":"object","nullable":true,"allOf":[{"$ref":"#/components/schemas/Build"}]},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/app-groups/{appGroupId}":{"get":{"summary":"Get App Group","tags":["App Groups"],"description":"Get an app group by its unique identifier.","operationId":"getAppGroup","parameters":[{"in":"path","name":"appGroupId","description":"The unique identifier for the app group.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppGroup"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete App Group

> Delete an app group by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/app-groups/{appGroupId}":{"delete":{"summary":"Delete App Group","tags":["App Groups"],"description":"Delete an app group by its unique identifier.","operationId":"deleteAppGroup","parameters":[{"in":"path","name":"appGroupId","description":"The unique identifier for the app group.","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"App Group deleted"},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Update App Group

> Update an app group by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"UploadAppGroup":{"type":"object","required":["platform","apps"],"properties":{"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}}}},"BuildQuery":{"type":"object","minProperties":1,"properties":{"buildId":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app."},"versionName":{"type":"string","description":"The version name, as specified by the app. In Android this is the versionName, in iOS this is the CFBundleShortVersionString."},"buildNumber":{"type":"string","description":"The build number, as specified by the app. In Android this is the versionCode, in iOS this is the CFBundleVersion."},"tags":{"type":"array","default":[],"items":{"type":"string"}}}},"AppGroup":{"type":"object","required":["id","platform","created","createdBy","apps","resolvedBuilds"],"properties":{"id":{"type":"string","description":"The unique identifier for the App Group."},"name":{"type":"string","description":"The name of the App Group."},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app-group was created.","type":"string","format":"date-time"},"updated":{"description":"The date the app-group was last updated.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app group.","type":"string"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app group was last played.","type":"string","format":"date-time"},"apps":{"type":"array","items":{"$ref":"#/components/schemas/BuildQuery"}},"resolvedBuilds":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedBuild"}}}},"ResolvedBuild":{"type":"object","nullable":true,"allOf":[{"$ref":"#/components/schemas/Build"}]},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/app-groups/{appGroupId}":{"patch":{"summary":"Update App Group","tags":["App Groups"],"description":"Update an app group by its unique identifier.","operationId":"updateAppGroup","parameters":[{"in":"path","name":"appGroupId","description":"The unique identifier for the app group.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAppGroup"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppGroup"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Apps

## List Apps

> List all apps in the account, with associated metadata.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"AppCollection":{"type":"object","properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/App"}}}},"App":{"type":"object","required":["appId","latestBuild","platform","created","createdBy","updated","updatedBy"],"properties":{"appId":{"type":"string","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier."},"name":{"type":"string","description":"The latest app name, as specified by the latest version."},"iconUrl":{"type":"string","description":"The latest app icon, as specified by the latest version.","format":"uri"},"latestBuild":{"description":"The latest build number for the app determined by its latest version and build number","allOf":[{"$ref":"#/components/schemas/Build"}]},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app.","type":"string"},"updated":{"description":"The date the app was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app was last played.","type":"string","format":"date-time"}}},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps":{"get":{"summary":"List Apps","tags":["Apps"],"description":"List all apps in the account, with associated metadata.","operationId":"listApps","parameters":[{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of results to return per page","schema":{"type":"integer"}},{"in":"query","name":"search","description":"The search term to filter by, example \"My App\".","schema":{"type":"string"}},{"in":"query","name":"platform","description":"The platform to filter by","schema":{"type":"string","enum":["ios","android"]}},{"in":"query","name":"appId","description":"The unique identifier for an app to filter by. On Android this is the package name, on iOS this is the bundle identifier.","schema":{"type":"string"}},{"in":"query","name":"versionName","description":"The version name to filter by, example \"1.0.0\". This is the CFBundleShortVersionString on iOS and versionName on Android.","schema":{"type":"string"}},{"in":"query","name":"buildNumber","description":"The build number to filter by, example \"1\", this is the versionCode on Android and CFBundleVersion on iOS.","schema":{"type":"integer","format":"int32"}},{"in":"query","name":"note","description":"The notes to filter by, example \"This is a note\".","schema":{"type":"string"}},{"in":"query","name":"tags","description":"The tags to filter by, example \"tag1,tag2\".","schema":{"type":"string"}},{"in":"query","name":"disabled","description":"The disabled status to filter by, example \"true\".","schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/AppCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get app icon by its unique identifier and platform

> Get app icon by its unique identifier and platform

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}}},"paths":{"/v2/apps/{platform}/{appId}/icon":{"get":{"summary":"Get app icon by its unique identifier and platform","description":"Get app icon by its unique identifier and platform","tags":["Apps"],"parameters":[{"name":"appId","in":"path","required":true,"description":"Unique identifier for the app.","schema":{"type":"string"}},{"name":"platform","in":"path","required":true,"description":"The platform of the app. Either ios or android","schema":{"type":"string","enum":["ios","android"]}}],"responses":{"307":{"description":"Redirect to the icon image file","headers":{"Location":{"content":{"text/plain":{"schema":{"type":"string","enum":["/app/icon/path"]}}}}}}}}}}}
```

## Get App

> Get an app by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"App":{"type":"object","required":["appId","latestBuild","platform","created","createdBy","updated","updatedBy"],"properties":{"appId":{"type":"string","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier."},"name":{"type":"string","description":"The latest app name, as specified by the latest version."},"iconUrl":{"type":"string","description":"The latest app icon, as specified by the latest version.","format":"uri"},"latestBuild":{"description":"The latest build number for the app determined by its latest version and build number","allOf":[{"$ref":"#/components/schemas/Build"}]},"platform":{"type":"string","enum":["ios","android"]},"created":{"description":"The date the app was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the app.","type":"string"},"updated":{"description":"The date the app was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the app.","type":"string"},"lastPlayed":{"description":"The time the app was last played.","type":"string","format":"date-time"}}},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps/{platform}/{appId}":{"get":{"summary":"Get App","tags":["Apps"],"description":"Get an app by its unique identifier.","operationId":"getApp","parameters":[{"in":"path","name":"appId","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier.","required":true,"schema":{"type":"string"}},{"in":"path","name":"platform","description":"The platform to filter by","required":true,"schema":{"type":"string","enum":["ios","android"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete App

> Delete an app by its app identifier. This removes all associated builds.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps/{platform}/{appId}":{"delete":{"summary":"Delete App","tags":["Apps"],"description":"Delete an app by its app identifier. This removes all associated builds.","operationId":"deleteApp","parameters":[{"in":"path","name":"appId","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier.","required":true,"schema":{"type":"string"}},{"in":"path","name":"platform","description":"The platform to filter by","required":true,"schema":{"type":"string","enum":["ios","android"]}}],"responses":{"204":{"description":"App deleted"},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{}}}}}}}}
```

## List All Versions for a given App

> List all version names of a given app. The default paging limit is 1000 items per page.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"AppVersionCollection":{"type":"object","required":["versions"],"properties":{"versions":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps/{platform}/{appId}/versions":{"get":{"summary":"List All Versions for a given App","tags":["Apps","Versions"],"description":"List all version names of a given app. The default paging limit is 1000 items per page.","operationId":"listAppVersions","parameters":[{"in":"path","name":"platform","description":"The platform to filter by","required":true,"schema":{"type":"string","enum":["ios","android"]}},{"in":"path","name":"appId","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier.","required":true,"schema":{"type":"string"}},{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of items to return per page","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/AppVersionCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## List All Tags for a given App

> List all tags of a given app. The default paging limit is 1000 items per page.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"TagCollection":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps/{platform}/{appId}/tags":{"get":{"summary":"List All Tags for a given App","tags":["Apps","Tags"],"description":"List all tags of a given app. The default paging limit is 1000 items per page.","operationId":"listAppTags","parameters":[{"in":"path","name":"platform","description":"The platform to filter by","required":true,"schema":{"type":"string","enum":["ios","android"]}},{"in":"path","name":"appId","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier.","required":true,"schema":{"type":"string"}},{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of items to return per page","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/TagCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# App Builds

## List All Builds for a given App

> List all builds for a given app, with associated metadata. The default paging limit is 100 items per page.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"BuildCollection":{"type":"object","properties":{"builds":{"description":"A collection of builds.","type":"array","items":{"$ref":"#/components/schemas/Build"}}}},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/apps/{platform}/{appId}/builds":{"get":{"summary":"List All Builds for a given App","tags":["App Builds"],"description":"List all builds for a given app, with associated metadata. The default paging limit is 100 items per page.","operationId":"listBuilds","parameters":[{"in":"path","name":"platform","description":"The platform of the app. Either ios or android","required":true,"schema":{"type":"string"}},{"in":"path","name":"appId","description":"The unique identifier for the app. On Android this is the package name, on iOS this is the bundle identifier.","required":true,"schema":{"type":"string"}},{"in":"query","name":"versionName","description":"The version name to filter by, example \"1.0.0\".","schema":{"type":"string"}},{"in":"query","name":"buildNumber","description":"The build number to filter by, example \"1\". This is the versionCode on Android and CFBundleVersion on iOS.","schema":{"type":"string"}},{"in":"query","name":"tags","description":"The tags to filter by, example \"tag1,tag2\".","schema":{"type":"string"}},{"in":"query","name":"latest","description":"Only return the latest version if more than one version matches the filter.","schema":{"type":"boolean"}},{"in":"query","name":"page","description":"The index of the desired page","schema":{"type":"number"}},{"in":"query","name":"limit","description":"The number of items to return per page","schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/BuildCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Builds

## Get a specific build by its unique identifier.

> Get a specific build by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/builds/{buildId}":{"get":{"summary":"Get a specific build by its unique identifier.","tags":["Builds"],"description":"Get a specific build by its unique identifier.","operationId":"getBuild","parameters":[{"name":"buildId","in":"path","required":true,"description":"Unique identifier for the build.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Build"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Build not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete Build

> Delete a build by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/builds/{buildId}":{"delete":{"summary":"Delete Build","tags":["Builds"],"description":"Delete a build by its unique identifier.","operationId":"deleteBuild","parameters":[{"in":"path","name":"buildId","description":"The unique identifier for the build.","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Build deleted","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Updates an existing build.

> Update a build by its unique identifier.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"UploadBuild":{"allOf":[{"$ref":"#/components/schemas/UploadBuildCommon"},{"oneOf":[{"$ref":"#/components/schemas/UploadBuildUrl"},{"$ref":"#/components/schemas/UploadBuildMultipart"}]}]},"UploadBuildCommon":{"type":"object","properties":{"tags":{"type":"array","default":[],"items":{"type":"string"},"description":"Tags for this specific build. Useful for identifying & filtering a certain build. You can add up to 20 tags, each with up to 20 characters."},"note":{"type":"string","description":"A note for your own purposes, will appear on your management dashboard."}}},"UploadBuildUrl":{"type":"object","properties":{"url":{"type":"string","description":"A publicly accessible link to your .zip, .tar.gz, or .apk file."}}},"UploadBuildMultipart":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The app file to upload."}}},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/builds/{buildId}":{"patch":{"summary":"Updates an existing build.","tags":["Builds"],"description":"Update a build by its unique identifier.","operationId":"updateBuild","parameters":[{"in":"path","name":"buildId","description":"The unique identifier for the build.","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadBuild"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Build"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get builds

> List all builds in the account, with associated metadata. The default page size is 100 items

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"BuildCollection":{"type":"object","properties":{"builds":{"description":"A collection of builds.","type":"array","items":{"$ref":"#/components/schemas/Build"}}}},"Build":{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"The unique identifier for the build."},"appId":{"type":"string","description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},"platform":{"type":"string","enum":["ios","android"]},"name":{"description":"The app name, as specified by the specific build. This is the CFBundleDisplayName on iOS and the application label on Android.","type":"string"},"iconUrl":{"description":"The app icon, as specified by the specific build.","type":"string","format":"uri"},"versionName":{"description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","type":"string"},"buildNumber":{"description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","type":"string"},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}},"note":{"description":"The note associated with the app version.","type":"string"},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"lastPlayed":{"description":"The time the build was last played.","type":"string","format":"date-time"},"metadata":{"description":"The platform specific metadata associated with the build.","oneOf":[{"type":"object","properties":{"android":{"$ref":"#/components/schemas/AndroidMetadata"}}},{"type":"object","properties":{"ios":{"$ref":"#/components/schemas/IosMetadata"}}}]}}},"AndroidMetadata":{"type":"object","properties":{"packageName":{"type":"string"},"name":{"type":"string"},"versionName":{"type":"string"},"versionCode":{"type":"integer","format":"int64"},"minSDKVersion":{"type":"integer","format":"int32"},"targetSDKVersion":{"type":"integer","format":"int32"},"architectures":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}},"localizations":{"type":"array","items":{"type":"string"}}}},"IosMetadata":{"type":"object","properties":{"CFBundleId":{"type":"string"},"CFBundleName":{"type":"string"},"CFBundleDisplayName":{"type":"string"},"MinimumOSVersion":{"type":"string"},"DTPlatformName":{"type":"string"},"CFBundleVersion":{"type":"string"},"CFBundleShortVersionString":{"type":"string"},"CFBundleSupportedPlatforms":{"type":"array","items":{"type":"string"}},"UIRequiredDeviceCapabilities":{"type":"array","items":{"type":"string"}},"DTSDKName":{"type":"string"},"UsageDescriptions":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"object","additionalProperties":{"type":"string"}}]}},"UISupportedInterfaceOrientations":{"type":"array","items":{"type":"string"}},"UISupportedInterfaceOrientationsIpad":{"type":"array","items":{"type":"string"}},"CFBundleURLTypes":{"type":"array","items":{"$ref":"#/components/schemas/CfBundleUrlType"}},"Localizations":{"type":"array","items":{"type":"string"}},"Frameworks":{"type":"array","items":{"type":"string"}},"Architectures":{"type":"array","items":{"type":"string"}}}},"CfBundleUrlType":{"type":"object","properties":{"CFBundleURLName":{"type":"string"},"CFBundleURLSchemes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/builds":{"get":{"summary":"Get builds","tags":["Builds"],"description":"List all builds in the account, with associated metadata. The default page size is 100 items","operationId":"listAllBuilds","parameters":[{"in":"query","name":"appId","schema":{"type":"string"},"description":"The unique identifier for the app build. On Android this is the package name, on iOS this is the bundle identifier."},{"in":"query","name":"buildId","description":"The unique identifier for the build.","schema":{"type":"string"}},{"in":"query","name":"platform","description":"The platform the app targets.","schema":{"type":"string","enum":["ios","android"]}},{"in":"query","name":"buildNumber","description":"The build number, as specified by the build. In Android this is the versionCode, in iOS this is the CFBundleVersion.","schema":{"type":"integer","format":"int32"}},{"in":"query","name":"versionName","description":"The version name, as specified by the build. In Android this is the versionName, in iOS this is the CFBundleShortVersionString.","schema":{"type":"string"}},{"in":"query","name":"tags","description":"The tags to filter by. Comma separated","schema":{"type":"string"}},{"in":"query","name":"limit","description":"The number of items to return. Default is 100.","schema":{"type":"integer","format":"int32","minimum":1,"maximum":200}},{"in":"query","name":"pageNumber","description":"The page number to return. Default is 1.","schema":{"type":"integer","format":"int32","minimum":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/BuildCollection"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Upload a new build

> Upload a new build to Appetize. This will create a new app if one does not already exist, or update an existing app with a new build version.\<br/>\<br/> You can upload a file via a url (application/json), or submit your file directly (multipart/form).\<br/>\<br/>Note that the build's metadata (ie. appId) is updated asynchronously after the request completes.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/builds":{"post":{"tags":["Builds"],"summary":"Upload a new build","description":"Upload a new build to Appetize. This will create a new app if one does not already exist, or update an existing app with a new build version.<br/><br/> You can upload a file via a url (application/json), or submit your file directly (multipart/form).<br/><br/>Note that the build's metadata (ie. appId) is updated asynchronously after the request completes.","requestBody":{"content":{"application/json":{"schema":{"allOf":[{"type":"object","required":["url","platform"],"properties":{"url":{"type":"string","description":"A publicly accessible link to your .zip, .tar.gz, or .apk file."},"platform":{"type":"string","enum":["ios","android"],"description":"Platform the app targets."},"tags":{"type":"array","default":[],"items":{"type":"string"},"description":"Tags for this specific build. Useful for identifying & filtering a certain build. You can add up to 20 tags, each with up to 20 characters."}}}]}},"multipart/form":{"schema":{"allOf":[{"type":"object","required":["file","platform"],"properties":{"file":{"type":"string","format":"binary","description":"The app file to upload."},"platform":{"type":"string","enum":["ios","android"],"description":"Platform the app targets ('ios' or 'android')."},"tags":{"type":"string","format":"array","description":"String representing an array of strings. Tags for this specific build. Useful for identifying & filtering a certain build. Ie. ['latest', 'beta']<br/><br/>You can add up to 20 tags, each with up to 20 characters."}}}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","required":["id","created"],"properties":{"id":{"type":"string","description":"Unique identifier for the build."},"created":{"description":"The date the build was created.","type":"string","format":"date-time"},"createdBy":{"description":"The user who created the build.","type":"string"},"updated":{"description":"The date the build was last updated.","type":"string","format":"date-time"},"updatedBy":{"description":"The user who last updated the build.","type":"string"},"note":{"type":"string","description":"A note for your own purposes, will appear on your management dashboard."},"tags":{"description":"The tags associated with the app version.","type":"array","items":{"type":"string"}}}}]}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get build icon by its unique identifier

> Get build icon by its unique identifier

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}}},"paths":{"/v2/builds/{buildId}/icon":{"get":{"summary":"Get build icon by its unique identifier","description":"Get build icon by its unique identifier","tags":["Builds"],"parameters":[{"name":"buildId","in":"path","required":true,"description":"Unique identifier for the build.","schema":{"type":"string"}}],"responses":{"307":{"description":"Redirect to the icon image file","headers":{"Location":{"content":{"text/plain":{"schema":{"type":"string","enum":["/build/icon/path"]}}}}}}}}}}}
```


# Identity Providers

## GET /v2/identity-providers

> List all identity providers

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"IdentityProviderList":{"type":"object","properties":{"providers":{"type":"array","items":{"$ref":"#/components/schemas/IdentityProvider"}}}},"IdentityProvider":{"type":"object","required":["id","key","type","config","created","createdBy"],"properties":{"id":{"type":"string","description":"The unique identifier for the Identity Provider"},"key":{"type":"string","description":"URL-safe, human-readable identifier"},"type":{"type":"string","enum":["saml","oidc"]},"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigResponse"},{"$ref":"#/components/schemas/OidcConfig"}]},"created":{"type":"string","format":"date-time","description":"Timestamp when the provider was created"},"createdBy":{"type":"string","description":"Username or token label that created the provider"},"updated":{"type":"string","format":"date-time","description":"Timestamp of the last update"},"updatedBy":{"type":"string","description":"Username or token label that last modified the provider"}}},"SamlConfigResponse":{"allOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"type":"object","properties":{"certExpires":{"type":"string","format":"date-time","readOnly":true},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated SAML providers (also used for IdP-initiated login)","readOnly":true}}}]},"SamlConfigRequest":{"type":"object","required":["entryPoint","issuer","cert"],"properties":{"entryPoint":{"type":"string","format":"uri","description":"Login URL where authentication requests are sent"},"issuer":{"type":"string","description":"Unique identifier for the application identity provider"},"cert":{"type":"string","description":"X.509 public certificate used to verify SAML assertions"}}},"OidcConfig":{"type":"object","required":["discoverUrl","clientId","clientSecret"],"properties":{"discoverUrl":{"type":"string","format":"uri","description":"The base URL of the OIDC provider. This should point to the discovery document root (e.g., https://accounts.example.com)."},"clientId":{"type":"string","description":"The client identifier registered with the OIDC provider."},"clientSecret":{"type":"string","description":"The client secret issued by the OIDC provider."},"scope":{"type":"string","description":"Space-separated list of scopes to request during the authorization flow."},"nonce":{"type":"string","description":"Optional nonce value to include in the authorization request."},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated OIDC providers","readOnly":true}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/identity-providers":{"get":{"summary":"List all identity providers","tags":["Identity Providers"],"operationId":"listIdentityProviders","parameters":[{"in":"query","name":"page","description":"The page number of results","schema":{"type":"integer"}},{"in":"query","name":"limit","description":"The number of results to return per page","schema":{"type":"integer"}}],"responses":{"200":{"description":"A list of configured identity providers","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/IdentityProviderList"}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## POST /v2/identity-providers

> Create a new identity provider

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"IdentityProviderCreate":{"type":"object","required":["type","config"],"properties":{"type":{"type":"string","enum":["saml","oidc"]},"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"$ref":"#/components/schemas/OidcConfig"}]}}},"SamlConfigRequest":{"type":"object","required":["entryPoint","issuer","cert"],"properties":{"entryPoint":{"type":"string","format":"uri","description":"Login URL where authentication requests are sent"},"issuer":{"type":"string","description":"Unique identifier for the application identity provider"},"cert":{"type":"string","description":"X.509 public certificate used to verify SAML assertions"}}},"OidcConfig":{"type":"object","required":["discoverUrl","clientId","clientSecret"],"properties":{"discoverUrl":{"type":"string","format":"uri","description":"The base URL of the OIDC provider. This should point to the discovery document root (e.g., https://accounts.example.com)."},"clientId":{"type":"string","description":"The client identifier registered with the OIDC provider."},"clientSecret":{"type":"string","description":"The client secret issued by the OIDC provider."},"scope":{"type":"string","description":"Space-separated list of scopes to request during the authorization flow."},"nonce":{"type":"string","description":"Optional nonce value to include in the authorization request."},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated OIDC providers","readOnly":true}}},"IdentityProvider":{"type":"object","required":["id","key","type","config","created","createdBy"],"properties":{"id":{"type":"string","description":"The unique identifier for the Identity Provider"},"key":{"type":"string","description":"URL-safe, human-readable identifier"},"type":{"type":"string","enum":["saml","oidc"]},"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigResponse"},{"$ref":"#/components/schemas/OidcConfig"}]},"created":{"type":"string","format":"date-time","description":"Timestamp when the provider was created"},"createdBy":{"type":"string","description":"Username or token label that created the provider"},"updated":{"type":"string","format":"date-time","description":"Timestamp of the last update"},"updatedBy":{"type":"string","description":"Username or token label that last modified the provider"}}},"SamlConfigResponse":{"allOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"type":"object","properties":{"certExpires":{"type":"string","format":"date-time","readOnly":true},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated SAML providers (also used for IdP-initiated login)","readOnly":true}}}]},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/identity-providers":{"post":{"summary":"Create a new identity provider","tags":["Identity Providers"],"operationId":"createIdentityProvider","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityProviderCreate"}}}},"responses":{"201":{"description":"Created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityProvider"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## GET /v2/identity-providers/{idpId}

> Get an identity provider by ID

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"parameters":{"idpId":{"name":"idpId","in":"path","required":true,"schema":{"type":"string"}}},"schemas":{"IdentityProvider":{"type":"object","required":["id","key","type","config","created","createdBy"],"properties":{"id":{"type":"string","description":"The unique identifier for the Identity Provider"},"key":{"type":"string","description":"URL-safe, human-readable identifier"},"type":{"type":"string","enum":["saml","oidc"]},"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigResponse"},{"$ref":"#/components/schemas/OidcConfig"}]},"created":{"type":"string","format":"date-time","description":"Timestamp when the provider was created"},"createdBy":{"type":"string","description":"Username or token label that created the provider"},"updated":{"type":"string","format":"date-time","description":"Timestamp of the last update"},"updatedBy":{"type":"string","description":"Username or token label that last modified the provider"}}},"SamlConfigResponse":{"allOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"type":"object","properties":{"certExpires":{"type":"string","format":"date-time","readOnly":true},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated SAML providers (also used for IdP-initiated login)","readOnly":true}}}]},"SamlConfigRequest":{"type":"object","required":["entryPoint","issuer","cert"],"properties":{"entryPoint":{"type":"string","format":"uri","description":"Login URL where authentication requests are sent"},"issuer":{"type":"string","description":"Unique identifier for the application identity provider"},"cert":{"type":"string","description":"X.509 public certificate used to verify SAML assertions"}}},"OidcConfig":{"type":"object","required":["discoverUrl","clientId","clientSecret"],"properties":{"discoverUrl":{"type":"string","format":"uri","description":"The base URL of the OIDC provider. This should point to the discovery document root (e.g., https://accounts.example.com)."},"clientId":{"type":"string","description":"The client identifier registered with the OIDC provider."},"clientSecret":{"type":"string","description":"The client secret issued by the OIDC provider."},"scope":{"type":"string","description":"Space-separated list of scopes to request during the authorization flow."},"nonce":{"type":"string","description":"Optional nonce value to include in the authorization request."},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated OIDC providers","readOnly":true}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/identity-providers/{idpId}":{"get":{"summary":"Get an identity provider by ID","tags":["Identity Providers"],"operationId":"getIdentityProvider","parameters":[{"$ref":"#/components/parameters/idpId"}],"responses":{"200":{"description":"Identity provider config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityProvider"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## DELETE /v2/identity-providers/{idpId}

> Delete an identity provider by ID

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"parameters":{"idpId":{"name":"idpId","in":"path","required":true,"schema":{"type":"string"}}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/identity-providers/{idpId}":{"delete":{"summary":"Delete an identity provider by ID","tags":["Identity Providers"],"operationId":"deleteIdentityProvider","parameters":[{"$ref":"#/components/parameters/idpId"}],"responses":{"204":{"description":"Deleted successfully"},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## PATCH /v2/identity-providers/{idpId}

> Update an existing identity provider

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"parameters":{"idpId":{"name":"idpId","in":"path","required":true,"schema":{"type":"string"}}},"schemas":{"IdentityProviderUpdate":{"type":"object","required":["config"],"properties":{"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"$ref":"#/components/schemas/OidcConfig"}]}}},"SamlConfigRequest":{"type":"object","required":["entryPoint","issuer","cert"],"properties":{"entryPoint":{"type":"string","format":"uri","description":"Login URL where authentication requests are sent"},"issuer":{"type":"string","description":"Unique identifier for the application identity provider"},"cert":{"type":"string","description":"X.509 public certificate used to verify SAML assertions"}}},"OidcConfig":{"type":"object","required":["discoverUrl","clientId","clientSecret"],"properties":{"discoverUrl":{"type":"string","format":"uri","description":"The base URL of the OIDC provider. This should point to the discovery document root (e.g., https://accounts.example.com)."},"clientId":{"type":"string","description":"The client identifier registered with the OIDC provider."},"clientSecret":{"type":"string","description":"The client secret issued by the OIDC provider."},"scope":{"type":"string","description":"Space-separated list of scopes to request during the authorization flow."},"nonce":{"type":"string","description":"Optional nonce value to include in the authorization request."},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated OIDC providers","readOnly":true}}},"IdentityProvider":{"type":"object","required":["id","key","type","config","created","createdBy"],"properties":{"id":{"type":"string","description":"The unique identifier for the Identity Provider"},"key":{"type":"string","description":"URL-safe, human-readable identifier"},"type":{"type":"string","enum":["saml","oidc"]},"config":{"type":"object","oneOf":[{"$ref":"#/components/schemas/SamlConfigResponse"},{"$ref":"#/components/schemas/OidcConfig"}]},"created":{"type":"string","format":"date-time","description":"Timestamp when the provider was created"},"createdBy":{"type":"string","description":"Username or token label that created the provider"},"updated":{"type":"string","format":"date-time","description":"Timestamp of the last update"},"updatedBy":{"type":"string","description":"Username or token label that last modified the provider"}}},"SamlConfigResponse":{"allOf":[{"$ref":"#/components/schemas/SamlConfigRequest"},{"type":"object","properties":{"certExpires":{"type":"string","format":"date-time","readOnly":true},"cbUrl":{"type":"string","description":"Legacy callback URL for migrated SAML providers (also used for IdP-initiated login)","readOnly":true}}}]},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/identity-providers/{idpId}":{"patch":{"summary":"Update an existing identity provider","tags":["Identity Providers"],"operationId":"updateIdentityProvider","parameters":[{"$ref":"#/components/parameters/idpId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityProviderUpdate"}}}},"responses":{"200":{"description":"Updated provider","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentityProvider"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Reports

## Get usage report summary

> Gets a usage summary of all sessions for your account, including the number of sessions and their durations. Query parameters can be used for fine-tuned reports.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/reports/usage/summary":{"get":{"tags":["Reports"],"summary":"Get usage report summary","description":"Gets a usage summary of all sessions for your account, including the number of sessions and their durations. Query parameters can be used for fine-tuned reports.","parameters":[{"name":"groupByDate","in":"query","description":"Group sessions with a certain date range. You can also group by a custom number of days by passing `${int}d`.","required":true,"schema":{"type":"string","enum":["day","week","month","all","${int}d"]}},{"name":"dateAnchor","in":"query","description":"Must only be included if using a custom groupByDate number. Anchors the groupByDate to a custom UTC date.","required":false,"schema":{"type":"string","format":"date"}},{"name":"buildId","in":"query","description":"If set, only sessions for the given buildId will be included in the report.","required":false,"schema":{"type":"string","default":"all"}},{"name":"startDate","in":"query","description":"The start date of the report (UTC time & inclusive).","required":false,"schema":{"type":"string","format":"date","default":"12 months prior to today"}},{"name":"endDate","in":"query","description":"The end date of the report (UTC time & inclusive).","required":false,"schema":{"type":"string","format":"date","default":"today"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","required":["startDate","endDate","numSessions","minutes"],"properties":{"startDate":{"description":"Start date of this session grouping.","type":"string","format":"date"},"endDate":{"description":"End date of this session grouping.","type":"string","format":"date"},"buildId":{"description":"BuildId of the grouped sessions. Alternatively the standalone or app group id.","type":"string"},"numSessions":{"description":"Number of grouped sessions.","type":"number"},"minutes":{"description":"Sum of minutes from the grouped sessions.","type":"number"},"platform":{"description":"Platform used for the grouped sessions.","type":"string","enum":["ios","android"]},"name_latest":{"description":"Name of app or app group if the grouping was performed on an app or app group.","type":"string"},"appId_latest":{"description":"appId if the grouping was performed on an app.","type":"string"},"note_latest":{"description":"Note on the app or app group.","type":"string"},"day":{"description":"Date of the grouping. Included when groupByDate=day.","type":"string","format":"date"},"week":{"description":"Week of the grouping. Included when groupByDate=week.","type":"string"},"month":{"description":"Month of the grouping. Included when groupByDate=month.","type":"string","format":"date"}}}}}}},"text/csv":{"schema":{"type":"string","description":"Same properties as the JSON response, only in csv format."}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"406":{"description":"Unsupported Accept header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get report on concurrent usage

> Gets the maximum number of concurrent sessions over time periods. Query parameters can be used for fine-tuned reports.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/reports/usage/concurrency":{"get":{"tags":["Reports"],"summary":"Get report on concurrent usage","description":"Gets the maximum number of concurrent sessions over time periods. Query parameters can be used for fine-tuned reports.","parameters":[{"name":"platform","in":"query","description":"If set, only sessions for the given platform will be included in the report.","required":false,"schema":{"type":"string","default":"both","enum":["ios","android","both"]}},{"name":"startDate","in":"query","description":"The start date of the report (UTC time & inclusive).","required":false,"schema":{"type":"string","format":"date","default":"3 months prior to today"}},{"name":"endDate","in":"query","description":"The end date of the report (UTC time & inclusive).","required":false,"schema":{"type":"string","format":"date","default":"today"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","required":["maxConcurrent","startDate","endDate"],"properties":{"startDate":{"description":"Start date of this session grouping.","type":"string","format":"date"},"endDate":{"description":"End date of this session grouping.","type":"string","format":"date"},"maxConcurrent":{"description":"Maximum number of concurrent sessions in this period.","type":"number"},"platform":{"description":"Platform that the sessions were ran on.","type":"string","enum":["ios","android"]}}}}}}},"text/csv":{"schema":{"type":"string","description":"Same properties as the JSON response, only in csv format."}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"406":{"description":"Unsupported Accept header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get report on accounts

> Gets a report containing useful information about an account. E.g. the number of users associated with it

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}}},"paths":{"/v2/reports/accounts":{"get":{"tags":["Reports"],"summary":"Get report on accounts","description":"Gets a report containing useful information about an account. E.g. the number of users associated with it","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["numUsers"],"properties":{"numUsers":{"description":"Number of users associated with the account.","type":"number"}}}}}}}}}}}
```


# Service

## Get IP blocks

> Get IP blocks for Appetize streaming servers. These are the IPs from which your running apps will be making network calls. Use the IP blocks to whitelist access to your backend if it is not public.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[],"paths":{"/v2/service/ips":{"get":{"tags":["Service"],"summary":"Get IP blocks","description":"Get IP blocks for Appetize streaming servers. These are the IPs from which your running apps will be making network calls. Use the IP blocks to whitelist access to your backend if it is not public.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ipv4":{"description":"List of ipv4 blocks.","type":"array","items":{"type":"string"}}}}}}}}}}}}
```

## Get available devices

> Get the list of available devices and operating systems.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[],"paths":{"/v2/service/devices":{"get":{"tags":["Service"],"summary":"Get available devices","description":"Get the list of available devices and operating systems.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"ID of the device (ie. for device selection in url params)."},"name":{"type":"string","description":"Name of the device"},"platform":{"type":"string","enum":["ios","android"],"description":"OS that the device runs."},"osVersions":{"type":"array","items":{"type":"string"},"description":"Array of os versions."}}}}}}}}}}}}
```


# Sessions

## Get sessions

> Gets sessions. Query parameters can be used for filtering sessions.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"SessionLog":{"type":"object","properties":{"targetId":{"type":"string"},"sessionRequestedTime":{"type":"string","format":"date-time","nullable":true},"startTime":{"type":"string","format":"date-time","nullable":true},"userConnectedTime":{"type":"string","format":"date-time","nullable":true},"frameTime":{"type":"string","format":"date-time","nullable":true},"appLaunchTime":{"type":"string","format":"date-time","nullable":true},"endTime":{"type":"string","format":"date-time","nullable":true},"closeTime":{"type":"string","format":"date-time","nullable":true},"sessionLengthSeconds":{"type":"number"},"queueTimeSeconds":{"type":"number"},"referrer":{"type":"string","nullable":true},"referrerHostname":{"type":"string","nullable":true},"sessionToken":{"type":"string","nullable":false},"clientLanguage":{"type":"string","nullable":true},"clientUserAgent":{"type":"string","nullable":true},"versionCode":{"type":"number"},"device":{"type":"string"},"osVersion":{"type":"string"},"rotation":{"type":"number"},"debug":{"type":"boolean"},"proxy":{"type":"string","nullable":true},"warmSession":{"type":"boolean"},"queued":{"type":"boolean"},"noVideo":{"type":"boolean"},"location":{"type":"array","items":{"type":"number"}},"user":{"type":"string","nullable":true},"userCountry":{"type":"string"},"userCity":{"type":"string"},"userAgent":{"type":"string","nullable":true},"platform":{"type":"string"},"params":{"type":"object","additionalProperties":true},"target":{"type":"object","properties":{"name":{"type":"string","nullable":true},"id":{"type":"string","nullable":true},"versionCode":{"type":"string","nullable":true},"versionName":{"type":"string","nullable":true},"appId":{"type":"string","nullable":true},"bundle":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}}}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/sessions":{"get":{"tags":["Sessions"],"summary":"Get sessions","description":"Gets sessions. Query parameters can be used for filtering sessions.","parameters":[{"name":"page","in":"query","description":"The page number of results","schema":{"type":"integer"}},{"name":"limit","in":"query","description":"The number of results to return per page","schema":{"type":"integer","default":50}},{"name":"startDate","in":"query","description":"The start date of the report (UTC time & inclusive).","required":true,"schema":{"type":"string","format":"date","default":"12 months prior to today"}},{"name":"endDate","in":"query","description":"The end date of the report (UTC time & inclusive).","required":false,"schema":{"type":"string","format":"date","default":"today"}},{"name":"search","in":"query","description":"Text search for sessions. Can be build id, app id, app name, etc.","required":false,"schema":{"type":"string"}},{"name":"targetId","in":"query","description":"Only include sessions for this target ID","required":false,"schema":{"type":"string"}},{"name":"appId","in":"query","description":"Only include sessions for this App ID","required":false,"schema":{"type":"string"}},{"name":"bundle","in":"query","description":"Only include sessions for this bundle","required":false,"schema":{"type":"string"}},{"name":"buildTags","in":"query","description":"Only include builds with these tags","schema":{"type":"string"}},{"name":"buildVersionName","in":"query","description":"Only include builds with this version name","schema":{"type":"string"}},{"name":"device","in":"query","description":"Only include sessions that used this device","required":false,"schema":{"type":"string"}},{"name":"platform","in":"query","description":"Only include sessions for this platform","required":false,"schema":{"type":"string","enum":["ios","android"]}},{"name":"osVersion","in":"query","description":"Only include sessions that used this osVersion. Can be specific version or just major.","required":false,"schema":{"type":"string"}},{"name":"users","in":"query","description":"Only include sessions from these users. Must be admin role, otherwise this will always be yourself.","required":false,"schema":{"type":"string"}},{"name":"sessionToken","in":"query","description":"Find the session by token","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort sessions by this field","required":false,"schema":{"type":"string","enum":["targetId","appId","bundle","buildVersionName","device","platform","osVersion","users","sessionToken","startTime"],"default":"startTime"}},{"name":"order","in":"query","description":"Order the sort by ascending or descending","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/SessionLog"}}}}]}},"text/csv":{"schema":{"type":"string","description":"All properties from the JSON response, only in csv format."}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden","content":{"application/json":{}}},"406":{"description":"Unsupported Accept header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Get session attachment

> Gets a session attachment

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/sessions/{sessionToken}/attachment":{"get":{"tags":["Sessions"],"summary":"Get session attachment","description":"Gets a session attachment","parameters":[{"name":"sessionToken","in":"path","description":"The session token of the session to get the attachment for","required":true,"schema":{"type":"string"}},{"name":"type","in":"query","description":"The type of attachment to get","required":true,"schema":{"type":"string","enum":["network-captures","debug-logs"]}}],"responses":{"307":{"description":"Redirect to the attachment file","headers":{"Location":{"content":{"text/plain":{"schema":{"type":"string","enum":["/session/attachment/path"]}}}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Tags

## List Tags

> List all tags in the account, that is currently assigned to a build. The default paging limit is 1000 items per page.

```json
{"openapi":"3.0.3","info":{"title":"Appetize V2 API Reference","version":"2"},"servers":[{"url":"https://api.appetize.io"}],"security":[{"jwt":[]},{"apiToken":[]}],"components":{"securitySchemes":{"apiToken":{"type":"apiKey","in":"header","name":"X-API-KEY","description":"Use an API token for authentication via X-API-KEY header"}},"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"nextPage":{"type":"number","nullable":true,"format":"int32"}}},"TagCollection":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"message":{"description":"Error message.","type":"string"}}}}},"paths":{"/v2/tags":{"get":{"summary":"List Tags","tags":["Tags"],"description":"List all tags in the account, that is currently assigned to a build. The default paging limit is 1000 items per page.","operationId":"listTags","parameters":[{"in":"query","name":"pageNumber","description":"The index of the desired page","schema":{"type":"number"}},{"in":"query","name":"limit","description":"The number of items to return per page","schema":{"type":"number"}},{"in":"query","name":"search","description":"The search term to filter by, example \"My Tag\".","schema":{"type":"string"}},{"in":"query","name":"platform","description":"The platform to filter by","schema":{"type":"string","enum":["ios","android"]}},{"in":"query","name":"appId","description":"The unique identifier for an app to filter by. On Android this is the package name, on iOS this is the bundle identifier.","schema":{"type":"string"}},{"in":"query","name":"buildId","description":"The unique identifier for the build.","schema":{"type":"string"}},{"in":"query","name":"buildNumber","description":"The build number, as specified by the app. In Android this is the versionCode, in iOS this is the CFBundleVersion.","schema":{"type":"string","format":"int32"}},{"in":"query","name":"versionName","description":"The version name to filter by, example \"1.0.0\".","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"$ref":"#/components/schemas/TagCollection"}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```




---

[Next Page](/llms-full.txt/1)

