# Pandium Documentation

Learn how to setup and configure Pandium integrations. Get technical information on all our connectors and understand key terminology.

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-type="content-ref"></th><th data-type="content-ref"></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Pandium Overview</td><td><a href="/pages/-MfJoMAGZ5PrKWMKxnaV">/pages/-MfJoMAGZ5PrKWMKxnaV</a></td><td></td><td></td><td></td><td><a href="/files/MF1MPsIax5g9FG3ALmhV">/files/MF1MPsIax5g9FG3ALmhV</a></td></tr><tr><td>Getting Started</td><td><a href="/pages/tKC8agCZafykfFiWkrwc">/pages/tKC8agCZafykfFiWkrwc</a></td><td></td><td></td><td></td><td><a href="/files/amctjYO7dGQLwLaNaNTZ">/files/amctjYO7dGQLwLaNaNTZ</a></td></tr><tr><td>Connectors</td><td><a href="/pages/miD65GDmGdisnIVazQiY">/pages/miD65GDmGdisnIVazQiY</a></td><td></td><td></td><td></td><td><a href="/files/DFDDD8cLx4FcUCCFkC6Q">/files/DFDDD8cLx4FcUCCFkC6Q</a></td></tr><tr><td>Integration Development Kit (IDK)</td><td><a href="/pages/ZWgILEKwDPZmC3oDsN9T">/pages/ZWgILEKwDPZmC3oDsN9T</a></td><td><a href="/pages/FKl0jKs4C3KXAmRFo3fM">/pages/FKl0jKs4C3KXAmRFo3fM</a></td><td><a href="/pages/fJXG9xEByvp7zFhmgMdF">/pages/fJXG9xEByvp7zFhmgMdF</a></td><td><a href="/pages/qMG02zRsOeebbeRTvvrv">/pages/qMG02zRsOeebbeRTvvrv</a></td><td><a href="/files/Hj3RdRKiMDNtP8ehKLtA">/files/Hj3RdRKiMDNtP8ehKLtA</a></td></tr><tr><td>Marketplaces</td><td><a href="/pages/hPiPYDNCpHtOKrP4UWFu">/pages/hPiPYDNCpHtOKrP4UWFu</a></td><td><a href="/pages/dSM2ex7WtFfI3t1BQCwb">/pages/dSM2ex7WtFfI3t1BQCwb</a></td><td></td><td></td><td><a href="/files/WqAgdKcIN5ycHmESztd0">/files/WqAgdKcIN5ycHmESztd0</a></td></tr><tr><td>Partners</td><td><a href="/pages/3vMThSKof7ZKGJPe5vTB">/pages/3vMThSKof7ZKGJPe5vTB</a></td><td></td><td></td><td></td><td><a href="/files/80Ba3amPPRuPCSdJccJw">/files/80Ba3amPPRuPCSdJccJw</a></td></tr><tr><td>Reference</td><td><a href="/pages/BE87MixtcX7QoIaFyghu">/pages/BE87MixtcX7QoIaFyghu</a></td><td><a href="/pages/-MfJreJ6cQ1vqbKclC_Y">/pages/-MfJreJ6cQ1vqbKclC_Y</a></td><td></td><td></td><td><a href="/files/bX3oezXocFhRAUxrvoza">/files/bX3oezXocFhRAUxrvoza</a></td></tr></tbody></table>

## Support

<support@pandium.com>

## Other Resources

[Pandium Website](https://www.pandium.com/)

[Blog](https://www.pandium.com/blog)

[Podcast](https://www.pandium.com/podcast)

[Book a Demo](https://www.pandium.com/demo)


# What is Pandium?

Pandium is an embedded integration platform (iPaaS) specifically designed for B2B SaaS companies that need to build and launch customer-facing integrations.

Our code-first integration platform enables its B2B customers to replicate the flexibility of in-house development without the technical overhead, DevOps maintenance, and headcount normally required.

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td></td><td><a href="/pages/H1fFf2U4CWENLRpme0pq">/pages/H1fFf2U4CWENLRpme0pq</a></td><td><a href="/files/7z4jgjdv3U6LPSvDuNKD">/files/7z4jgjdv3U6LPSvDuNKD</a></td></tr><tr><td></td><td><a href="/pages/iym5462jc2asIGDVKfic">/pages/iym5462jc2asIGDVKfic</a></td><td><a href="/files/0S5hl8pXZBf4qHOBZiQ9">/files/0S5hl8pXZBf4qHOBZiQ9</a></td></tr><tr><td></td><td><a href="/pages/PiSAnaz5QdXQN6DWLltG#platform-structure">/pages/PiSAnaz5QdXQN6DWLltG#platform-structure</a></td><td><a href="/files/2ATwoWuGUdX7KYlvsr0x">/files/2ATwoWuGUdX7KYlvsr0x</a></td></tr><tr><td></td><td><a href="/pages/wegaLvCUYrLj3atdzwnn">/pages/wegaLvCUYrLj3atdzwnn</a></td><td><a href="/files/80Ba3amPPRuPCSdJccJw">/files/80Ba3amPPRuPCSdJccJw</a></td></tr></tbody></table>

## Learn More

If you’re new to Pandium and would like to learn more about the platform, [book a demo](https://www.pandium.com/demo) with one of our integration specialists. You can also visit our marketing site to get an overview of our [products](https://www.pandium.com/product-overview), review [customer case studies](https://www.pandium.com/customer-stories) and read our [blog](https://www.pandium.com/blog).

Visit [pandium.com](http://pandium.com)

<br>


# The Pandium Platform

Pandium helps SaaS companies deliver scalable native integrations while offloading authentication, hosting, and logging.

Pandium’s Integration Platform allows SaaS businesses to offer scalable, high-volume native integrations to their customers while Pandium manages the technical elements like authentication, hosting, and logging.

### Streamlined Development Process

The platform’s [Integration Hub](https://www.pandium.com/integration-hub) not only manages the foundational infrastructure and repetitive processes necessary for building, maintaining, and supporting user-facing integrations, but also alleviates the burden on developers by eliminating the need to handle authentication every time an integration is deployed to a customer.

Plus, once an integration to a particular system is set up in Pandium, there's no need to start from scratch for every new user or customer who wants that integration. This makes deploying to new users and updating integrations faster.

### Automated Building and Versioning

Pandium’s Integration Hub and [pre-built authentication connectors](https://docs.pandium.com/quick-start/key-terminology#connector) enable developers to create highly configurable and reusable integrations using the tools already in their tech stack. The platform is compatible with all major repositories, including GitHub, Gitlab, Azure DevOps, and Bitbucket.

<figure><img src="https://lh7-us.googleusercontent.com/_I6hbrWLNLbBMGLG6En7OIWvrqppW_otMD0Dn6vrkpCIEFwOKoZxcnqm1EFwCr3NFuj1zioz0ryRtac1YCp6Sa0UCVwB_CVlfiFRfGoAvYp42nq1_NM4Bmm-Z6Awq0f6jS-aYigNH0oWZOKfQP1Nwoc" alt="" width="563"><figcaption></figcaption></figure>

Developers code the business logic to craft integration configurations and sync options for integrations in any common language. They then connect their repository to Pandium where building, releasing, and versioning is automated.

Learn more about our platform structure [here](https://docs.pandium.com/getting-started/anatomy-of-an-integration).<br>

<figure><img src="https://lh7-us.googleusercontent.com/mPOF8WPFUQes9qFN1L58MECWrZHFUGdBViMenSFIJSNW38E4ZWvu2Cf7PpJ3_fBqUGH_S75VKvjbrL9ilhs2KaJ66V8uLf6EUaTm2_iZlI4AoCiYhProivF405fHbC7iwx14VCxscOiDkxX26cQjjUE" alt="" width="375"><figcaption><p>After an integration is created in Pandium, different instances of that integration can be deployed to different customers via the Pandium UI.</p></figcaption></figure>

### Efficiency Through Reusability

Once Pandium has used the connection to an integration's remote repository to access its code, end users will be able to install the integration. When they do, the Pandium UI will present them with sync and configuration options.

Developers (or non-technical teams) can specify what flows or integration versions to deploy to users and reuse these connectors, versions, and configurations in Pandium to launch future integrations faster.

To learn more about how to build an integration using Pandium, check out our [Quick Start Guide](https://docs.pandium.com/quick-start/quick-start).

### Add-On Products

The Pandium platform also offers optional add-ons products to support B2B integration go-to-market strategies and technology partnerships.

Including an embeddable[ In-App Marketplace](https://www.pandium.com/integration-hub) where end users can download integrations, manage configurations, schedule syncs, and monitor their installed integrations via a white-labeled, self-serve dashboard.

Pandium also offers a fully customizable [Public Gallery](https://www.pandium.com/public-gallery) to showcase integrations and partners on their website. The Public Gallery is fully optimized for SEO and lead capture.

Both the In-App Marketplace and Gallery offer collaboration features for partners. Partners can join the Pandium platform through invitations or be manually added by admins (i.e. Pandium customers).

Once in, partners can submit integrations, marketing materials, and details for their integration listing on the In-App Marketplace and Gallery for feedback and approval.

Professional services through our [Integration Team](https://www.pandium.com/integration-team) are also available. This is a group of expert integration engineers with over 40+ years of experience across use cases and industries.

These engineers act as an extension of your team and quickly become experts in your specific API(s) and integration use cases.

<br>


# What Companies Use Pandium For

Pandium’s platform provides SaaS companies with scalable, high-volume integrations without the infrastructure overhead.

* Launch highly configurable, reusable, and dynamic native integrations.
* Automate release management. [Release management](https://docs.pandium.com/quick-start/key-terminology#release) options and versioning within the platform make it easy to update your integrations and track changes.
* Set customer-specific deployment without having to build the infrastructure to enable and manage this.
* Give customers the ability to build custom user experiences with the [Pandium API](https://docs.pandium.com/reference/pandium-api).
* Deploy integrations to specific customers or empower clients to discover and install integrations via an embedded In-App Marketplace UI in your app.
* Showcase your ecosystem of integrations and partners to customers, partners, and prospects via our In-App Marketplace and Public Gallery products.
* Give customers more visibility into their integrations and enable them to install apps, manage configurations, define sync schedules, and view logs via a customizable marketplace experience.
* Manage relationships with technology partners. Invite partners to submit integrations, marketing materials, and documentation for their tile within your marketplace.


# Platform Structure

Understand the technical architecture of the Pandium platform. Pandium takes care of the infrastructure required for customer-facing integrations.

### Platform Structure

![](/files/-MgzhgAAt79aQ9g1ITLe)

The Pandium platform equips businesses with the required infrastructure to efficiently launch native integrations. Pandium includes prebuilt authentication via our [Connectors ](https://docs.pandium.com/getting-started/key-terminology#connector)and secrets management to facilitate system-to-system authentication.

Pandium supports both public and private APIs and allows full utilization of the target system's API for integration development. SFTP or even direct database connections can also be used to build integrations using Pandium, if granted the necessary access.

Developers create custom business logic (ETL) to craft integration configurations and sync options that align with specific requirements. Connecting their repository to Pandium automates building, [release](https://docs.pandium.com/quick-start/key-terminology#release), and versioning processes.

Within Pandium's UI, sync and configuration options are surfaced and accessible, and authentication is handled through pre-built connectors.

Our platform allows users to select specific flows or integration versions for deployment, streamlining integration CI/CD management via a [source control](https://docs.pandium.com/build/source-control) process that connects repositories to the platform.

This approach facilitates the reuse of connectors, versions, and configurations. Speeding up deployment of future integrations for different customers or [tenants](https://docs.pandium.com/getting-started/key-terminology#tenant).

Pandium also lets users modify or update integration configurations as needed through the App/Script in the repository.

### The Pandium YAML

The PANDIUM.yaml takes the business logic that specifies the sync and configuration options and surfaces them on the Pandium UI where they can be used to deploy customer-specific integrations. Optionally, these sync & configurations options can be shown to customers via the Pandium In-App Marketplace.

Learn more about the Pandium.yaml [here](https://docs.pandium.com/build/anatomy-of-an-integration/pandium.yaml-spec).


# Users of Pandium

Understand the different types of users that can exist in the Pandium Integration Platform, including platform, engineering, partner and end-customer users.

## Platform User

A Pandium platform user is an employee of a B2B software company that has access to the Integration Hub part of the platform. This user may be a customer success rep, partnership manager, product manager and/or marketer. Platform users troubleshoot, configure, and onboard end-customers to new integrations. A platform user may also run reports, monitor behavior, or set up marketplaces or marketing content.

## Engineering User

A Pandium engineering user is the software developer who is building integrations that run on the platform. An engineering user may be an employee of your customer, a partner's engineer, a third-party developer like a development agency, or a member of Pandium's professional service team. Engineering users typically provision new integrations and then perform development locally. From there, an engineering user's interaction with the platform is typically related to version management, testing, and troubleshooting.

## Partner User

A partner user is a third-party user that is submitting integrations to our customer. Often a partner user is another SaaS company that wishes to be listed in your Pandium-powered marketplace; however, a partner user could also be a development or marketing agency. Partner users can submit marketing content and/or apps for approval and publication in your first-party marketplace, and can also see tenants and runs associated with integrations they own. Partners are invited by Pandium users and access can be revoked at any time.

## Customer End User

An end-customer user is the business user that is the customer of Pandium's customer. End-customers are often referred to as tenants, and they are the ultimate consumers of apps running on Pandium. End-users typically do not know that Pandium exists but interact with the platform through a white-labeled UI that is hosted by Pandium, living inside your application. End-customers authenticate into connected systems and manage their own configuration options as specified by the engineering user of the platform.


# Anatomy of an Integration

A more technical look into the structure of Pandium and a deeper dive into the workings of how integrations are hosted and run on our platform.

![We handle all the devops while you focus on the App](/files/uzwh3HgGA8pNE1VYyVIx)

On Pandium, integrations are constructed as a simple Posix compliant [command line interface](https://en.wikipedia.org/wiki/Command-line_interface) application (CLI). The CLI should read tenant configuration and connector secrets from [environment variables](/getting-started/anatomy-of-an-integration/environment-variables). Any logs you wish to display to your end users should be written to [`stderr`](/getting-started/anatomy-of-an-integration/environment-variables/stderr).

One can pass information to the next run by writing a json encoded string to [`stdout`](/getting-started/anatomy-of-an-integration/environment-variables/stdout). On the next run it will be injected in as an environment variable named`PAN_CTX_LAST_RUN_STDOUT` or `PAN_CTX_LAST_SUCESSFUL_RUN_STDOUT.`


# Run Triggers

Control how Pandium integrations sync data with cron, manual, webhook, and API run triggers, including payload handling and debouncing per tenant.

### What Are Run Triggers?

End users can initiate syncs on a Pandium tenant in multiple ways: via a regular schedule, by clicking a button in the In-App Marketplace, or by sending a webhook. Pandium customers can also design their own mechanisms to trigger syncs by using the Pandium API.

Integration scripts can be written to handle one or more of these methods; it is possible for a single integration script to handle more than one trigger type. The code can even be customized to respond differently depending on the run trigger method.

Additionally, API and webhook triggers can contain information in the payload body that Pandium makes available to the integration code.

If planning for an integration to only ever run via scheduled job or be triggered manually, run triggers can safely be ignored.

#### Run Triggers In Depth

Pandium represents the four ways that users can trigger runs via four different modes: API, Webhook, Cron, and Manual.

Crons are scheduled syncs. Manual syncs are triggered after clicking a button within either the Integration Hub or In-App Marketplace. API and webhooks triggers are self-explanatory.

Pandium debounces triggers in order to prevent more than one sync happening per tenant at any given time. If a sync request comes in while the integration script is running for a tenant, Pandium will bundle the triggers and pass them into a new run that will start when the current run is completed.

Crons are scheduled in their own, discrete queue and given priority over other types of runs. This ensures scheduled syncs do not fall behind. Crons are only ever bundled with other cron jobs, but the other three trigger types can coexist on a single run trigger array.

On the other hand, if the integration is going to be receiving payloads when it is triggered by a webhook or API call, understanding run triggers and how to access them in your code is key.

### Run Triggers in Integration Code

Within a run's [context](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables/stdout#dynamic-sync-variables), the run triggers array is stored in the [environment variable](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables) PAN\_CTX\_RUN\_TRIGGERS. A trigger is a JSON object that contains information about how the run started, including run mode (init or normal), trigger source, and payload data as well as any headers it was sent with.

If you're writing an integration that is receiving webhooks or API requests with payloads, the payload data is stored in a file that can be accessed using your language's built-in file reading functionality.

As an example, below is a script in Javascript that gets the run triggers/payloads from the Pandium context inside an integration:

```javascript
const runTriggers = JSON.parse(process.env.PAN_CTX_RUN_TRIGGERS)
const payloads = []
for (const trigger of runTriggers) {
      // Only webhooks and API triggers have payloads
      if trigger.mode === 'webhook') {
        const data = fs.readFileSync(trigger.payload.file, 'utf-8')
        // do something with data from payload
      }
}
```

### Run Trigger Examples

The run triggers exposed to your integration via environment variables will have one of the following modes: cron, manual, webhook, or api.

Below is an example of a webhook trigger:

```json
{
    "id": "4731234254284123",
    "source": "webhook",
    "payload": {
        "file": "sample/test_file.txt",
        "headers": {
            "User-Agent": "PostmanRuntime/7.29.0"
        }
    },
    "created_date": "2023-01-27T23:27:07Z",
    "mode": "webhook"
}
```

{% hint style="info" %}
Should your application require a webhook secret key for verification purposes, you can obtain this within your Pandium Admin Dashboard via the Connector Info on your [Tenant Details Page](https://docs.pandium.com/integration-hub/managing-and-updating-tenants-in-the-admin-portal).
{% endhint %}

Below is an example of an API trigger:

```json
{
    "id": "12345678",
    "source": "api",
      "payload": {
        "file": "sample/test_file.txt",
        "headers": {
            "User-Agent": "PostmanRuntime/7.29.0"
        }
    },
    "created_date": "2023-06-06T10:11:50-04:00",
    "mode": "normal"
}
```

Below is an example of a manual trigger:

```json
{
    "id": "12345678",
    "source": "manual",
    "created_date": "2023-06-06T10:11:50-04:00",
    "mode": "init"
}
```

Below is an example using cron:

```json
{
    "id": "example_id",
    "source": "cron",
    "created_date": "2023-06-06T10:11:50-04:00",
    "mode": "normal"
}
```

###


# Run Failures

Learn about Pandium run failures, their causes (integration, refresh, platform, timeout), and how to troubleshoot or report issues effectively.

### What Are Run Failures?

In the instance that an error occurs during a sync, there are four possible status that will be reflected for a run:

* Failed (Integration Issue)
* Failed (Refresh)
* Failed (Platform Issue)
* Failed (Timeout)

#### Integration Issues

If something fails inside your integration code, in order to properly reflect a failed status, it is the responsibility of the integration development team to exit the process with a failed status code. Below are some examples codes that can be used for different languages:

<table><thead><tr><th>Language</th><th width="223.78125">Set &#x26; Continue</th><th>Immediate Exit</th></tr></thead><tbody><tr><td>Node.js</td><td><code>process.exitCode = 1</code></td><td><code>process.exit(1)</code></td></tr><tr><td>Java</td><td>❌</td><td><code>System.exit(1)</code></td></tr><tr><td>C#</td><td><code>Environment.ExitCode = 1</code></td><td><code>Environment.Exit(1)</code></td></tr><tr><td>PHP</td><td>❌</td><td><code>exit(1)</code> or <code>die(1)</code></td></tr></tbody></table>

**Refresh Failures**

There may be instances where a run fails due to a error during the refresh process. Disconnecting and reconnecting the tenant will typically help resolve these types of failures. However, if you continue to experience refresh failures even after performing these initial troubleshooting steps, please reach out to your Technical Account Manager or submit a support request to <support@pandium.com> to further assist with investigatory efforts.

**Platform Issue**

There may be times when a failure is the result of an internal issue impacting Pandium's infrastructure. Depending on the severity, the Pandium team will report any known errors to our status page - <https://status.pandium.com/>. However, it's possible the platform failure is isolated to a specific tenant(s). If no status related to platform failures is visible on the Pandium status page, please reach out to your Technical Account Manager or submit a support request to <support@pandium.com>.

**Failed (Timeout)**

Timeout errors will occur when the tenant run job exceeds the ten minute limit.<br>


# PANDIUM.yaml

Learn how to structure and configure your PANDIUM.yaml file to build, run, and customize integrations on the Pandium platform using schema and UI settings.

The PANDIUM.yaml provides information to build and execute an integration script on the Pandium platform. It also defines the configuration options and UI, via the configs section, where tenant end users will input information vital to the integration run.

## Name and Location

When [setting](/integration-hub/setting-up-your-first-integration-tile) up an internal integration on the Integration Hub, it will ask for a Repository Path. Specify the name and location of the file with respect to the root of your integration code repository. If not set, by default, Pandium will look for a file named PANDIUM.yaml in the root directory.

<figure><img src="/files/oth02bAfeayqpcBTz17z" alt="" width="563"><figcaption><p>Repository Settings</p></figcaption></figure>

## Structure

All Pandium.yaml files are required to start with the following 5 mappings.

```yaml
version: 0.4
base: python:3.7 # Node, Ruby, PHP, Java, GO
build: pipenv install 
run: pipenv run python -m hubspot2s3

configs: {} # details to follow
```

* **version**: This should be at the top of the file. Currently this should always be set to `0.4`.
  * Please note we also support `0.5` and `0.8`
    * Starting in 0.8, stdout capture now preserves JSON formatting to prevent parsing issues in downstream tools.
* **base:** Use this property to tell us what language and version you have coded your integration in. Format should be `<language>:<version>`
  * acceptable values include:`closure`, `kotlin`, `node`, `python`, `ruby`, `java`, `php`, `.net`
  * The version of the language should follow the the language using a colon.
    * Examples `python:3.7` or `java:11`

{% hint style="success" %}
We strive to support all commonly used languages and at least the long-term supported version of the language if such a version is defined. If you don't see your preferred language or version, please let your Technical Account Manger know and we will work on getting support built out right away.
{% endhint %}

* build: This should be set to how this integration packages its code and gets all of its dependencies installed. Pandium strives to support all common packing and dependency management tools. This includes pip (python), npm (node), composer (php), maven (java), etc.
* run: This property tells Pandium how to run the integration’s code at run time. This should be the same command used to invoke the integration script on a local command line. For example, if an integration had a script named “main.py” `python -m main.py` would be used to execute it both locally and on Pandium.

## Configs

The connections setting page is used to configure an integration tenant to run according to its end user’s needs. The selections made by a tenant end user on that page are passed to the integration in each run as environmental variables. To learn more about how and why Integration Hub users can configure a tenant, review [this article](https://docs.pandium.com/integration-hub/managing-and-updating-tenants-in-the-admin-portal).

The options displayed to an Integration Hub user on the connections settings page are determined by the configs of the PANDIUM.yaml. The `configs` property needs to include both the `schema` and `uischema`.

Each of the connection settings’ configurations must be defined under `schema` `properties` as a string to object mapping.

* The string specifies the configuration’s name. The name of the environmental variable used to pass a configuration to an integration’s run is the property name prefixed with `PAN_CFG_`. For example, if the property name is `config_name`, then the integration will be able to access the tenant end user’s selection for config\_name with `PAN_CFG_CONFIG_NAME`.
* The object to which the property name is mapped must contain the `type` prop. Depending on a property’s type, other props could be specified, but none are required. Review [this article](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/schema) to learn about different types of schema properties and additional props they can be given.

Each of the connection settings’ configurations must also be listed in the [uischema](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/uischema) `elements`. The `uischema` determines how and where each configuration is displayed on the connection settings page. Some UISchema elements only serve formatting purposes, while others display a specific configuration defined under schema properties. The scope of such UISchema elements must reference the name of the corresponding schema property. Review [this article](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/uischema) to learn about different types of UISchema elements and additional props they can be given.

Below is an example of the rest of a PANIDIUM.yaml with four static configs (not including the required information in example code above)

In the connections settings page, the end user will be presented with the four configurations defined under the `schema` properties.

The configurations will be labeled as described under the `uischema`.

Notice that this example includes `schema` properties and `uischema` elements with props that have not yet been discussed (e.g `default` and `admin`). To learn more about those and other props, review the reference guides on the different types for entries of the [schema](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/schema) and [UISchema](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/uischema) along with the props each type can take.

```yaml
version: 0.4
base: python:3.7 # Node, Ruby, PHP, Java, GO
build: pipenv install
run: pipenv run python -m hubspot2s3
configs:
  schema:
    properties:
      s3_bucket_name:
        type: string
      s3_file_name:
        type: string
      make_contact:
        type: boolean
        default: true
      make_company:
        type: boolean
    type: object
  uischema:
    elements:
    - label: S3 Bucket Name
      scope: '#/properties/s3_bucket_name'
      type: Control
    - label: S3 File Name
      scope: '#/properties/s3_file_name'
      type: Control
    - label: Make Contact?
      scope: '#/properties/make_contact'
      type: Control
      admin: true
    - label: Make Company?
      scope: '#/properties/make_company'
      type: Control
    type: VerticalLayout
```

These options would be displayed on the connections settings page inside the Integration Hub like this:

<figure><img src="/files/aYvTCR59ycFmAxQZ7Onx" alt=""><figcaption><p>PANDIUM.yaml in the Connections Settings Page</p></figcaption></figure>

The configs in the example are all static, meaning the options for every tenant will be the same. Dynamic configs have options populated from an API or database, so they are different for each tenant. Review [this article](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations) to learn how the PANDIUM.yaml and the standard out of an init sync can be set up to create dynamic configurations.


# Schema

Define PANDIUM.yaml schema properties for Boolean, integer, number, string, enum, array, and multi-select configs to control integration connection settings in Pandium.

The PANDIUM.yaml’s schema properties are a collection of string to object mappings that determine the name and type of each configuration in the connection settings page. The object to which the property name is mapped must contain the `type` prop. Pandium currently supports the following types, many of which can take additional props:

### Boolean

```yaml
bool_input:
  type: boolean
  default: true
```

In this example `bool_input` is the name of the config. When presented to the end user, they will see a checkbox that is checked because the default is set to true. When a run happens this config will be injected into the environment as `PAN_CFG_BOOL_INPUT`.

| prop        | values        | note |
| ----------- | ------------- | ---- |
| **default** | true or false |      |

![How the end-user will see this when combined with UISchema](/files/-Mi8QVRjXOjtg1WheaTQ)

### Integer

```yaml
integer_input:
  type: integer
  default: 0
  min: -1
  max: 10
```

In this example, `integer_input` is the name of the config. When presented to the end user, they will see a number selector that is restricted to integers, with the value 0 being presented as the starting value. The end user will be able to enter an integer between -1 and 10. When a run happens, this config will be injected into the Environment as `PAN_CFG_INTEGER_INPUT`.

| prop        | values     | note              |
| ----------- | ---------- | ----------------- |
| **default** | an integer |                   |
| **min**     | an integer | negatives allowed |
| **max**     | an integer |                   |

![How the end-user will see this when combined with UISchema](/files/-Mi8QRVLLg74MMn5ZuBe)

### Number

```yaml
number_input:
  type: number
  default: 1.3333333
```

In the above example, `number_input` is the name of the config. When presented to the Integration Hub user, they will see a number selector with the value 1.3333 presented as the starting value. When a Run happens this config will be injected into the Environment as `PAN_CFG_NUMBER_INPUT`.

| prop        | values     | note              |
| ----------- | ---------- | ----------------- |
| **default** | any number |                   |
| **min**     | any number | negatives allowed |
| **max**     | any number |                   |

![How the end-user will see this when combined with UISchema](/files/-Mi8QMs7cW-ysCJ1SyDK)

### String

```yaml
string_input:
  type: string
```

In the above example, string\_input is the name of the config. When presented to the Integration Hub user, they will see a text box that is empty being the starting value. When a Run happens this config will be injected into the Environment as PAN\_CFG\_STRING\_INPUT.

| prop        | values                                        | note                                                                                                                                                                                                     |
| ----------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **default** | any valid unicode string                      |                                                                                                                                                                                                          |
| **format**  | null \|\| "date" \|\| "time" \|\| "date-time" | <p>when null or undefined control renders as a text box. <em>date</em> renders as a date picker</p><p><em>time</em> renders as a time picker and <em>date-time</em> renders a date and time control.</p> |
| **enum**    | an array of values                            | if you add an enum to the control, it will render as a dropdown.                                                                                                                                         |

![How the end-user will see this when combined with UISchema](/files/-Mi8QDoD6u_3AVNPLGXj)

### Enum

```yaml
string_enum_input:
  enum:
  - option 1
  - option 2
  - option 3
  type: string
```

In the above example, string\_enum\_input is the name of the config. When presented to the Integration Hub user, they will see a select box allowing them to choose between the given enum values. When a Run happens this config will be injected into the Environment as PAN\_CFG\_STRING\_ENUM\_INPUT.

![How the end-user will see this when combined with UISchema](/files/-Mi8Q41lSC9ueA7OgyFZ)

### Labeled Enum

```yaml
labeled_enum:
  type: string
  oneOf:
    - title: Option 1 label
      const: option1
    - title: Option 2 label
      const: option2
    - title: Option 2 label
      const: option2
```

In the above example, labeled\_enum is the name of the config. When presented to the Integration Hub user, they will see a select box with labeled values. When a Run happens this config will be injected into the Environment as PAN\_CFG\_LABELED\_ENUM.

<figure><img src="/files/jPlekhoWSm3bVJgR1pAt" alt=""><figcaption><p>How the Integration Hub user will see this when combined with UISchema</p></figcaption></figure>

### Array/Object

```yaml
  array_input:
    type: array
    items:
      type: object
      properties:
        date:
          type: string
          format: date
        enum:
          enum:
          - foo
          - bar
          type: string
        message:
          type: string
          maxLength: 5
```

In the above example, `array_input`is the name of the config. When presented to the user they will see a line with a combination of the three properties that make up each object in the list, the UI allows them to add or remove objects to the array. When a Run happens this config will be injected into the Environment as `PAN_CFG_ARRAY_INPUT`

![How the end-user will see this when combined with UISchema](/files/-Mi8RQXScF4FzhAFfbBC)

### Multi-Select Enum

```yaml
string_enum_multi_input:
  items:
    options:
      - label: Option 1 label
        value: option1
      - label: Option 2 label
        value: option2
      - label: Option 2 label
        value: option2
```

| prop      | value                                                       | note |
| --------- | ----------------------------------------------------------- | ---- |
| **items** | options: a list of objects where each has a label and value |      |

### File Upload

```yaml
file_upload:
  type: string
```

In the above example, file\_upload is the name of the config. When presented to the Integration Hub or Marketplace user, they will see an option to import a CSV file . When a Run happens this config will be injected into the Environment as PAN\_CFG\_FILE\_UPLOAD.

The value for the `file_upload` config is a base64 encoded CSV file, so you will simply need to decode this in your integration should you choose to use it.

<figure><img src="/files/kNVPzju25xIWDoEx3lcg" alt=""><figcaption><p>How the Integration Hub user will see this when combined with UISchema</p></figcaption></figure>

{% hint style="info" %}
**Note:** Like all the schema property types described here, the configuration’s property must be referenced in the scope of a uischema element for it to be displayed on the connection settings page. Unlike other schema property types, the UISschema element type for a multi select uischema must be MultiSelectControl, rather than Control. To learn more about how the UISchema determines how the configurations are displayed review [this article](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/uischema).
{% endhint %}

### Putting it all together

```yaml
schema:
  type: object
  properties:
    bool_input:
      type: boolean
      default: true
    array_input:
      type: array
      items:
        type: object
        properties:
          date:
            type: string
            format: date
          enum:
            enum:
            - foo
            - bar
            type: string
          message:
            type: string
            maxLength: 5
    number_input:
      type: number
      default: 1.3333333
    string_input:
      type: string
    integer_input:
      max: 10
      min: -1
      type: integer
      default: 0
    string_enum_input:
      enum:
      - option 1
      - option 2
      - option 3
      type: string
```


# UiSchema

Configure PANDIUM.yaml UISchema elements to control how integration settings are labeled, ordered, and laid out on the Pandium connection settings page.

The PANDIUM.yaml's UISchema is a list of elements that determine how the configurations defined in the schema properties are displayed on the connection settings page e.g. the order of controls, their visibility, labels, and layout.

All UISchema elements must contain the type prop. Depending on that type other props may be available/required. Pandium currently supports the following types of UISchema elements:

### Control

A UISchema element with `type: Control` displays one of the configurations defined in the schema properties.

In addition to type, it takes these properties:

| prop        | values                                                                                                              | note                                                                                                                                                                 |
| ----------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **scope**   | [JSON schema reference value](https://spacetelescope.github.io/understanding-json-schema/structuring.html#reuse%22) | <p>Required.</p><p>Determines which schema property this UISchema element displays.</p><p>Has the following format <code>‘#/properties/\[property\_name]’</code></p> |
| **label**   | string                                                                                                              | <p>Not Required.</p><p>Used to customize the text seen for the control on the connections settings page.</p><p><br></p>                                              |
| **admin**   | boolean                                                                                                             | <p>Not Required.</p><p>When true, the element will only render for Integration Hub users.</p><p><br></p>                                                             |
| **options** | object                                                                                                              | <p>Not required. For full-width inputs, use<br><code>options:</code><br><code>trim: false</code></p>                                                                 |
|             |                                                                                                                     |                                                                                                                                                                      |

If the schema properties look like this:

```yaml
schema:
  type: object
  properties:
    bool_input:
      type: boolean
      default: true
```

Then, the UISchema element for the bool\_input property could look like this:

```yaml
type: Control
label: A Boolean Input
scope: "#/properties/bool_input"
```

It would then be displayed in the connection settings page like this:

!["A Boolean Input" is defined by the label property in the UISchema](/files/-Mi8QVRjXOjtg1WheaTQ)

### MultiSelectControl

A UISchema element with `type: MultiSelectControl` displays one of the configurations defined in the schema properties. This type will only render correctly if the Schema property referenced in its scope is formatted as a multi select enum.

In addition to type, it takes these properties:

| prop      | values                                                                                                              | note                                                                                                                                                                                                                                                                                    |
| --------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **scope** | [JSON schema reference value](https://spacetelescope.github.io/understanding-json-schema/structuring.html#reuse%22) | <p>Required for Control.</p><p>Determines which schema property this uischema element displays.</p><p>Has the following format <code>‘#/properties/\[property\_name]’</code></p><p>The schema property referenced in its scope must be formatted as a multi select enum.</p><p><br></p> |
| **admin** | boolean                                                                                                             | Not Required. When true, the element will only render for Integration Hub users.                                                                                                                                                                                                        |
|           |                                                                                                                     |                                                                                                                                                                                                                                                                                         |

If the schema property looks like this:

```yaml
string_enum_multi_input:
  items:
    options:
      - label: Option 1 label
        value: option1
      - label: Option 2 label
        value: option2
      - label: Option 2 label
        value: option2
```

Then, the UISchema element for the string\_enum\_multi\_input property coudl look like this:

```yaml
- type: MultiSelectControl
  scope: "#/properties/string_enum_multi_input"
```

Which would then be displayed like this on the connections page:

<figure><img src="/files/M7PEsgUJeAfrK5Az8H7x" alt=""><figcaption><p>MultiSelectControl UISchema Element</p></figcaption></figure>

### Label

A UISchema element with type: Label displays a label. This can be helpful when combined with UISchema elements that do not take the label prop (e.g. the MultiSelectControl)

In addition to type, it takes these properties:

| prop         | value   | note                                                                             |
| ------------ | ------- | -------------------------------------------------------------------------------- |
| **title**    | string  | Not Required.                                                                    |
| **subtitle** | string  | Not Required.                                                                    |
| **hintText** | string  | Not Required.                                                                    |
| **admin**    | boolean | Not Required. When true, the element will only render for Integration Hub users. |

Here is an example of a UISchema element with type: label

```yaml
- type: Label
  subtitle: Select Appropriate Options
  hintText: This is the hintText for the label element
  title: Multi Select Control
```

Which would be displayed like this on the connection settings page:

<figure><img src="/files/vRJCBsPX3VEg42JxdEZ2" alt=""><figcaption><p>Label UISchema element</p></figcaption></figure>

### Divider

A UISchema element with type: Divider displays a horizontal line. This can help visually organize and separate different sections of the connections setting page. This type of UISchema element does not take any additional properties.

Here is an example of UISchema element with type: Label

```yaml
- type: Divider
```

It would be displayed as a simple dividing line in the connection settings page.

### Vertical Layout

A UISchema element with type: VerticalLayout is used to list other UISchema elements vertically. Layout elements (i.e. VerticalLayout, HorizontalLayout, Section) can be combined and nested within one another.

In addition to type, it takes these properties:

| prop        | value                     | note      |
| ----------- | ------------------------- | --------- |
| **element** | List of UISchema elements | Required. |

Here is an example of UISchema element with type: Vertical Layout:

```yaml
type: VerticalLayout
elements:
- type: Control
  label: Name
  scope: "#/properties/name"
- type: Control
  label: Birth Date
  scope: "#/properties/birthDate"
```

Which would be displayed like so:

![Elements displayed vertically in the UI](/files/-MiMMqUunrRNKtiAPDAi)

### Horizontal Layout

A UISchema element with type: HorizontalLayout is used to list other UISchema elements side by side. Layout elements (i.e. VerticalLayout, HorizontalLayout, Section) can be combined and nested within one another.

In addition to type, it takes these properties:

| prop        | value                     | note      |
| ----------- | ------------------------- | --------- |
| **element** | List of UISchema elements | Required. |

Here is an example of UISchema element with type: HorizontalLayout:

```yaml
type: HorizontalLayout
elements:
- type: Control
  label: Name
  scope: "#/properties/name"
- type: Control
  label: Birth Date
  scope: "#/properties/birthDate"
```

In addition to type, it takes these properties:

![Elements displayed horizontally in the UI](/files/-MiMNDIXp7b76Eemlb0v)

### Section

A UISchema element with type: Section is used to list other UISchema elements vertically. It differs from the VerticalLayout because it can take more props (e.g. label, subtitle, hintText). Layout elements (i.e. VerticalLayout, HorizontalLayout, Section) can be combined and nested within one another.

In addition to type, it takes these properties:

| prop         | value                     | note          |
| ------------ | ------------------------- | ------------- |
| **element**  | List of UISchema elements | Required.     |
| **label**    | string                    | Not required. |
| **subtitle** | string                    | Not required. |
| **hintText** | string                    | Not required. |

Here is an example of UISchema element with type: Section:

```yaml
type: Section
label: My Group
type: Section
  label: First Section
  elements:
  - type: Control
    label: Name
    scope: "#/properties/name"
    options:
      trim: true
  - type: Control
    label: Birth Date
    scope: "#/properties/birthDate"
    options:
      trim: true
  hintText: This is the hint text of the first section.
  subtitle: This is the subtitle of the first section.
```

It would be displayed like this on the connections settings page:

<figure><img src="/files/Qx44de1RP3CjfRxe0E4d" alt=""><figcaption><p>Elements displayed in a Section UISchema Element</p></figcaption></figure>

### File Input

A UISchema element with type: File Input is used to provide users (both Admins and customer end-users) with the ability to import a CSV file.

Here is an example of UISchema element with type: File Upload:

```yaml
type: FileInput
label: File Upload
scope: "#/properties/file_upload"
```

### Combining Layout Elements

UISchema elements of the type VerticalLayout, HorizontalLayout, and Section can be combined and nested within one another as in this example:

```yaml
---
type: VerticalLayout
elements:
- type: Section
  label: First Section
  elements:
  - type: Control
    label: Name
    scope: "#/properties/name"
    options:
      trim: true
  - type: Control
    label: Birth Date
    scope: "#/properties/birthDate"
    options:
      trim: true
  hintText: This is the hint text of the first section.
  subtitle: This is the subtitle of the first section.
- type: Divider
- type: Section
  label: Second Section
  elements:
  - type: Control
    label: Option One
    scope: "#/properties/option1"
  - type: Control
    label: Option Two
    scope: "#/properties/option2"
  hintText: Note the horizontal line between the two section. You can add this to your
    own coniguration form by including a 'Divider' element.
  subtitle: The second section contains two boolean fields.
```

![Two sections in a vertical layout, seperated by a divider](https://user-images.githubusercontent.com/49411879/137545293-00ef9312-2268-4297-9d38-9296d96f0d31.png)


# Dynamic Configurations

Define dynamic configs in PANDIUM.yaml using schema definitions and init syncs that fetch tenant-specific options from APIs or databases for Pandium integrations.

{% hint style="info" %}
This page describes the `definitions` + init-sync pattern used by manifest **version 0.4**. On manifest **version 1.0**, dynamic options are sourced from [tenant metadata](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata) via `#/metadata/` references — still typically populated by an init sync. See [Using Metadata to Populate Dynamic Configs](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata#using-metadata-to-populate-dynamic-configs).
{% endhint %}

Typically when creating integration configurations, most fields will be static; that is, the options for the field will be the same for every tenant. However, it can be useful to include dynamic configurations, where the integration populates the options with values from an API or database, so they are different for each tenant.

To see an example of an integration that benefits from a dynamic config, and what it looks like for users on the Integration Hub or the In-App marketplace, review [this article](https://docs.pandium.com/integration-hub/managing-tenant-connection-settings#dynamic-configurations).

An Integration requires three key changes to add dynamic configurations, each of which will be explained in more detail below:

1. The PANDIUM.yaml schema property for a dynamic config needs to be given a reference to a list of options in the schema’s definitions.
2. The PANDIUM.yaml schema needs to have a definitions section with an entry for each of those references.
3. The integration’s code must have an init sync flow which fetches the tenant specific options for each dynamic config’s definition and prints them to the standard out.

Pandium will save the init sync’s standard out, and use it to provide the options on the Connection Settings page for each dynamic config.

### Adding a Definitions Reference to a Schema Property

Every configuration needs an entry under schema properties. If a configuration has multiple options, those options can be listed under a definitions entry which is referenced by the property’s $ref.

A configuration with options that are dynamically populated based on an API or database fetch must include the $ref prop in its schema properties entry.

Here are some examples of schema properties with the $ref prop:

```yaml
 properties:
      log_level:
        $ref: 'definitions/log_level_options'
        default: INFO
        type: string
      mailchimp_audience:
        $ref: '#/definitions/audiences'
        type: string 
      klaviyo_list:
        $ref: '#/definitions/lists' 
```

Notice that the type prop (usually required for all properties entries) can sometimes be omitted when the $ref prop is provided. This works when the property’s type can be inferred from the definition referenced by $ref.

### Adding Schema Definitions

A schema definitions section needs to be added to the PANDIUM.yaml when there are properties that reference definitions. Like the schema properties, definitions is a collection of string to object mappings.

The string is the definition’s name which can be referenced by a property’s $ref.

The object to which each definition entry is mapped specifies a list of options. If the options will be dynamically populated based on an API or database fetch, then the list will just have a placeholder in PANDIUM.yaml. That placeholder will be replaced with tenant specific options once they are fetched during an init sync.

Here is an example of a schema definition for a static config. Notice that the options are specified here in the PANDIUM.yaml because they do not need to be fetched, so the options for this config are the same for every tenant.

```yaml
   properties:
      log_level:
        $ref: '#/definitions/log_level_options'
        default: INFO
        type: string
    definitions:
      log_level_options:
        enum:
          - INFO
          - DEBUG
          - WARNING
          - ERROR
```

Static config options like these can also be listed within the property without referencing a definition. To see an example like that, look at the enum code samples of this article (link to PANDIUM.yaml reference article on schema properties, specifically the string section)

Here is an example of a schema definition for a dynamic config. The list of options only has one entry- the placeholder. The actual options will be fetched during an init sync so they can be different for each tenant.

```yaml
    properties:
      mailchimp_audience:
        $ref: '#/definitions/audiences'
        default: INFO
        type: string
    definitions:
      audiences:
        enum:
          - placeholder
```

Here is another example of a dynamic config’s schema definition. Notice that the type for these options is oneOf rather than strings in an enum.

```yaml
    properties:
      klaviyo_list:
        $ref: '#/definitions/lists
        default: INFO
        type: string
    definitions:
      lists:
        oneOf:
          - const: placeholder
            Title: Placeholder
```

### Adding an Init Sync to the Integration Script

Once a schema is prepared to receive dynamic data, the integration script provides that data when there is an init sync that prints a standard out.

In the script, the appropriate run mode can be found by accessing the context environment variable: PAN\_CTX\_RUN\_MODE . If the value of this environment variable is 'init', then the script can add logic to populate the dynamic configs.

When Pandium displays the Connection Settings page for an integration with dynamic configs it will populate the options for those configs using information loaded from the init sync’s last line output to[ stdout](https://docs.pandium.com/build/anatomy-of-an-integration/stdout).

Script Example:

```javascript
if (pandium.context.runMode === 'init') {
    const mcAudiences = await mcClient.getMany('audience');
    const kLists = await kClient.getMany('audience');
    const stdout = {
        // If a key in the standard out matches one of the PANDIUM.yaml's configs.schema.definitions its values will populate the dropdown for configs that reference that definition.
        'audiences': mcAudiences.map(({ name }) => name)
        'lists': kLists.map((list) => {const: list.id, title: list.name})    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

In the above example, a developer has added logic to do the following when PAN\_CTX\_RUN\_MODE is init:

1. Gather a list of Mailchimp audiences and Klaviyo Lists by calling each of their APIs.
2. Put those list and audience options into an object.
3. Print that object to the stdout as a JSON string at the end of the script.

Pandium saves the init sync’s standard out, and uses it to provide the options on the Connection Settings page for the dynamic configurations.

### Putting it All Together

Here is what a full PANDIUM.yaml could look like if it had all of the properties from the examples above:

```yaml
version: 0.4
base: node:20.9.0
build: npm install --production && npm i --save-dev @types/node && npm run build
run: node .
configs:
 schema:
   type: object
   
   definitions:
     log_level_options:
       enum:
         - INFO
         - DEBUG
         - WARNING
         - ERROR  
     audiences:
       enum:
         - placeholder
     lists:
       oneOf:
         - const: placeholder
           title: Placeholder

   properties:
     log_level:
       $ref: '#/definitions/log_level_options'
       default: INFO
       type: string
     mailchimp_audience:
       $ref: '#/definitions/audiences'
       type: string 
     klaviyo_list:
       $ref: '#/definitions/lists'

 uischema:
   type: VerticalLayout
   elements:

   - type: Section
     label: First Section
     elements:
     - type: Control
       label: Log Level
       scope: "#/properties/log_level"
     - type: HorizontalLayout
       elements:
       - type: Control
         label: MailChimp Audience
         scope: "#/properties/mailchimp_audience"
       - type: Control
         label: Klaviyo List
         scope: "#/properties/klaviyo_list"
```

After the init sync has been run, this .yaml would produce the following connections settings page on the Integration Hub:

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

To achieve the options in the screenshot above, the last line of stdout from the integration’s init sync would have printed:

```log
[OUT] {"audiences":["Friends","Family","Acquaintances"],"lists":[{"title":"Prospective Customers","const":1},{"title":"One Time Customers","const":2},{"title":"Loyal Customers","const":3}]}
```

For more detailed instructions on how to implement dynamic configurations review [this tutorial](https://docs.pandium.com/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-2).


# Dependent Selector Configurations

Create dependent selector configs in PANDIUM.yaml where dynamic dropdown options update based on a parent field’s value, using schema definitions and init sync outputs.

{% hint style="info" %}
This page describes the `definitions` + init-sync pattern used by manifest **version 0.4**. On manifest **version 1.0**, dependent selectors are sourced from [tenant metadata](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata) via `optionsMapPath: '#/metadata/<key>'` — still typically populated by an init sync. See [Using Metadata to Populate Dynamic Configs](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata#using-metadata-to-populate-dynamic-configs).
{% endhint %}

The last article explained how to make a dynamic configuration, a setting where the options are fetched from an API or database so they change from tenant to tenant

This article will explain how to make a dependent selector configuration, a setting where the options are fetched from an API or data base *and* vary based on the user's selection of an earlier config (which can also be dynamic)

### Here is an example of a dependent selector configuration:

A tenant settings page that allows the the user select their favorite food.

The options for the favorite food will be fetched from an API or database, so they will be different from tenant to tenant.

There will also be a food type configuration.

* The user's selection for food type will also affect what options are presented to the user when they are selecting their favorite food.
* The food type config is also dynamic, so the food type options will vary from tenant to tenant.

#### Here are what the options could look like to different users:

* Tenant A: food type options will be **Sweet** and **Savory**.

  <figure><img src="/files/QTRmoygp8BXzFAUOkgWE" alt=""><figcaption><p>The only food type option for Tenant A are "Sweet" and "Savory"</p></figcaption></figure>

  * If the user chooses food type **Sweet** then the favorite food options will be: **Chocolate**, **Caramel**, **Jello**

  <figure><img src="/files/nDXZncINqACLj58txEHV" alt=""><figcaption><p>When the user selects the "Sweet: food type, the options for favorite food are only sweet foods.</p></figcaption></figure>

  * If the user chooses food type **Savory** then the favorite food options will be: **Chips**, **Pizza**, **Bread**

  <figure><img src="/files/uPROwDl6CIyKMowyBu10" alt=""><figcaption><p>When the user selects the "Savory" food type, the options for favorite food are only savory foods.</p></figcaption></figure>
* Tenant B: food type options will be **Fruits** and **Veggies**.

  <div align="center" data-full-width="false"><figure><img src="/files/Xqi5ipjyFXH0t64FHEBt" alt=""><figcaption><p>The only food type option for Tenant B are "Fruits" and "Veggies"</p></figcaption></figure></div>
* If the user chooses food type **Fruits** then the favorite food options will be: **Strawberry**, **Banana**, **Kiwi**

  <figure><img src="/files/LRvA7P4qC0NCLD11NeI9" alt=""><figcaption><p>When the user selects the "Fruits" food type, the options for favorite food are only fruits.</p></figcaption></figure>
* If the user chooses food type **Veggies** then the favorite food options will be: **Carrot**, **Broccoli**, **Pepper**

  <figure><img src="/files/7Xm3Yc6XcNvuRdS4CmpP" alt=""><figcaption><p>When the user selects the "Veggies" food type, the options for favorite food are only vegetables.</p></figcaption></figure>

#### A dependent dynamic configuration is implemented by creating a parent selector and a dependent selector:

1. Create the **parent selector:** This is the dynamic configuration whose selected value determines the options available for the **dependent selector**.
   * In this example the **parent selector** is the `food_type`.
   * Here is a quick review on how to set up a dynamic configuration:
     1. Like other dynamic configurations, the `schema` property for this must have a reference to a list of options in the `schema`'s `definitions`.
        * In this example the `food_type` schema property must have a reference to `food_type_options`.
     2. Like other dynamic configurations, the schema needs to have a definitions section with an entry that has the options for the **parent selector**.
        * In this example the `schema` `definitions` must include a list of `food_type_options`.
     3. Like a normal configuration, the `uischema` `elements` section must have an entry for the **parent selector**.
        * In this example there must be a uischema element with `type: control` and `scope: '#/properties/food_type'`
     4. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the **parent selector** and prints them to the standard out.
        * Its value must be an array of objects, each of which has a `const` and `title` property. The `const` will be the ID of that option, and the `title` will be the name of the option.
        * In this example the standard out of an init sync must include `food_type_options`
          * It could look like this: `food_type_options = [`

            `{'const': 1, 'title': 'Fruits'},`

            `{'const': 2, 'title': 'Veggies'},`

            `]`
          * It could look like this: `food_type_options = [`

            `{'const': 3, 'title': 'Sweet'},`

            `{'const': 4, 'title': 'Savory'},`

            `]`
2. Create a **dependent selector:** This is the configuration whose options will depend on the **parent selector** and the values fetched from a database/API.
   * In this example the **dependent selector** is `favorite_food`.
   * Here is how a **dependent selector** is set up:
     1. Like a normal configuration, the `schema` property for this must have a `type`. The `type` for a dependent selector should be `string`.
     2. It must have two additional properties:
        1. optionsMapPath: This must reference the schema definition that has the options for the **dependent selector**.
           * In this example it will be `'#/definitions/food_options'`.
        2. `parentField`: This must reference the schema property for the **parent selector** which controls the options that will be present.
           * In this example it will be `'#/properties/food_type'`
     3. Like other dynamic configurations, the `schema` needs to have a `definitions` section with an entry that has the options for the **dependent selector**. It's type should be `object`.
        * In this example the `schema` `definitions` must include a list of `food_options`.
     4. Like a normal configuration, the `uischema` `elements` section must have an entry for the **dependent selector**, with the type Control.
     5. Like other dynamic configurations, the integration's code must have an init sync which fetches the tenant specific options for the **dependent selector** and prints them to the standard out.
        1. It's value must be a map of the option type IDs (from the **parent selector**) to a list of the options for that type.
        2. All the options for every list must be either entirely `string` or entirely `oneOf`(an objects with a `title` and `const`)
        3. In this example each option for every list will be a `string`.
        4. In this example the standard out of an init sync must include `food_options`
           * It could look like this `food_options = {`

             `1: ['Strawberry', 'Banana', 'Kiwi'],`

             `2: ['Carrot', 'Broccoli', 'Pepper']`

             `}`

             * It could look like this `food_options = {`

               `3: ['Chocolate', 'Caramel', 'Jello'],`

               `4: ['Chips', 'Pizza', 'Bread'],`

               `}`

### Putting it all together

The configs section of the PANDIUM.yaml would include this:

<pre class="language-yaml"><code class="lang-yaml">configs:

<strong>  schema:
</strong>    definitions:
      food_type_options:
        oneOf:
          - const: Placeholder
            title: Placeholder
      food_options:
        type: object
    properties:
      food_type:
        $ref: "#/definitions/food_type_options"
      favorite_food:
        type: string
        optionsMapPath: '#/definitions/food_options'
        parentField: '#/properties/food_type'

  uischema:
    type: VerticalLayout
    elements:
      # Parent
      - label: Food Type
        scope: '#/properties/food_type'
        type: Control
      # Dependent
      - label: Favorite Food
        scope: '#/properties/favorite_food'
        type: Control

</code></pre>

A TypeScript implementation of the init sync of the integration in could look like this:

```javascript
if (pandium.context.runMode === 'init') {
    const foods = await foodClient.getMany('food');
    /* foods is a list that could look like this: 
    [
     { type: 'Sweet', name: 'Chocolate' },
     { type: 'Sweet', name: 'Caramel' },
     { type: 'Sweet', name: 'Jello' },
     { type: 'Savory', name: 'Chips' },
     { type: 'Savory', name: 'Pizza' },
     { type: 'Savory', name: 'Bread' },
    ]
    */
    
    // Loop over the foods to identify the food types.
    const uniqueFoodTypes = foods.reduce((foodTypesList, currentFood) => {
      if(!foodTypesList.includes(currentFood.type)){
        foodTypesList.push(currentFood.type)
      }
      return foodTypesList
    }, [] )
    
    // Map each food type to the oneOf format
    const foodTypes = uniqueFoodTypes.map((type, index) => {
      return {
        const: index + 1,
        title: type
      }
    })
    /* foodTypes could look like this:
    [
     { const: '1', title: 'Sweet' },
     { const: '2', title: 'Savory' },
    ]
    */
    
    // Create a map from food type to ID, which can be used to organize foods into lists by type.
    const foodTypeToIdMap = foodTypes.reduce((typeToIdMap, foodType) => {
      typeToIdMap[foodType.title] = foodType.const
      return typeToIdMap
    }, {})
    /* foodTypeToIdMap could look like this:
    {
      'Sweet': '1',
      'Savory': '2'
    }
    */
    
    // Organize the foods into lists of their types
    foodListByTypeId = foods.reduce((foodListByType,currentFood) => {
      const typeId = foodTypeToIdMap[currentFood.type]
      if(!foodListByType[typeId]) {
        foodListByType[typeId] = [] 
      }
      foodListByType[typeId].push(currentFood.name)
      return foodListByType
    }, {} )
    /* foodListByTypeId could look like this:
    [
      '1': ['Chocolate', 'Caramel', 'Jello' ],
      '2': ['Chips', 'Pizza', 'Bread']
    ]
    */

    const stdout = {
      food_type_options: foodTypes,
      food_options: foodListByTypeId
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

### Another example

This example has a static **parent selector** and the options for the **dependent selector** will be `oneOf` rather than `string`.

The configs section of the PANDIUM.yaml would include this:

```yaml
configs:
  schema:
    type: object
    definitions:
      pokemon_types:
        oneOf:
          - const: Fire
            title: Fire
          - const: Water
            title: Water
          - const: Rock
            title: Rock
          - const: Flying
            title: Flying
      pokemon_options:
        type: object
    properties:
      pokemon_type:
        $ref: "#/definitions/pokemon_types"
        type: string
      pokemon_1:
        type: string
        optionsMapPath: "#/definitions/pokemon_options"
        parentField: "#/properties/pokemon_type"
      pokemon_2:
        type: string
        optionsMapPath: "#/definitions/pokemon_options"
        parentField: "#/properties/pokemon_type"

  uischema:
    type: VerticalLayout
    elements:
      - label: Pokemon Type
        scope: "#/properties/pokemon_type"
        type: Control
      - type: Section
        label: Pokemon Selection
        hintText: Choose your Pokemon
        elements:
          - label: First Pokemon
            scope: "#/properties/pokemon_1"
            type: Control
          - label: Second Pokemon
            scope: "#/properties/pokemon_2"
            type: Control
```

A TypeScript implementation of the init sync of this integration in could look like this:

```javascript
if (pandium.context.runMode === 'init') {
    const pokemon = await pokeClient.getMany('pokemon');
    /* pokemon is a list that could look like this: 
    [
     { type: 'Fire', name: 'Charmander', id: 1 },
     { type: 'Fire', name: 'Vulpix', id: 2  },
     { type: 'Water', name: 'Squirtle', id: 3  },
     { type: 'Water', name: 'Poliwag', id: 4  },
     { type: 'Rock', name: 'Geodude', id: 5  },
     { type: 'Rock', name: 'Onix', id: 6  },
     { type: 'Flying', name: 'Butterfree', id: 7 },
     { type: 'Flying', name: 'Pidgeot', id: 8  },
    ]
    */

    // The parent control, pokemon_type, has a static list of options: pokemon_types. 
    // so the unique options for pokemon_types do not need to be identified.
    const pokemonOptions = {
      Fire:[],
      Water:[],
      Rock: [],
      Flying: [],
    }

    // Add each pokemon to the list for the appropriate type.
    for (const currentPokemon of pokemon) {
      pokemonOptions[currentPokemon.type].push({
        title: currentPokemon.name,
        const: currentPokemon.id
      })
    }
 
    /* pokemonOptions could now look like this:
    {
      Fire: [ 
          { title: 'Charmander', const: '1'},  
          { title: 'Vulpix', const: '2' },
      ],
      Water: [ 
          { title: 'Squirtle', const: '3'},  
          { title: 'Poliwag', const: '4' },
      ],
      Rock: [ 
          { title: 'Geodude', const: '5'},  
          { title: 'Onix', const: '6' },
      ],
      Flying: [ 
          { title: 'Butterfree', const: '7'},  
          { title: 'Pidgeot', const: '8' },
      ]
    }
    */
    

    const stdout = {
      pokemon_options: pokemonOptions
    };
    // Print json string to stdout and end the script.
    console.log(JSON.stringify(stdout));
    return
}
```

### Dependent Selectors in Arrays

Dependent selectors can be used in arrays. The selector will look first for its parent in its own object, and if it doesn't find it there it will look up the next level in the schema. Consider the following setup:

```
configs:
  schema:
    type: object
    definitions:
      food_options:
        type: object
      pokemon_types:
        enum:
          - Fire
          - Water
          - Rock
          - Flying
      pokemon_options:
        type: object
    properties:
      food:
        type: string
        parentField: '#/properties/food_type'
        optionsMapPath: '#/definitions/food_options'
      food_type:
        enum:
          - fruit
          - Vegetable
      pokemon_list:
        type: array
        items:
          type: object
          properties:
            p_type:
              $ref: '#/definitions/pokemon_types'
              type: string
              title: Pokemon Type
            pokemon_name:
              type: string
              title: Pokemon
              parentField: '#/properties/p_type'
              optionsMapPath: '#/definitions/pokemon_options'
            food_for_pokemon:
              type: string
              title: Food
              parentField: '#/properties/food_type'
              optionsMapPath: '#/definitions/food_options'
  uischema:
    type: Section
    label: Fun with Dependent Configs
    subtitle: This is a test of dependent configs
    elements:
      - type: Control
        label: Food Type
        scope: '#/properties/food_type'
      - type: Control
        label: Pokemon List
        scope: '#/properties/pokemon_list'
    
```

Given dynamic configs as described above, this will render the following form:

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

Note that the second column in the table depends on the first, but the third column depends on the "food type" selector above.


# Tenant Metadata

Learn how to store tenant specific information for your customers.

{% hint style="info" %}
Tenant Metadata is available on PANDIUM.yaml manifest **version 1.0** and above.
{% endhint %}

### What is Tenant Metadata?

Tenant metadata lets your integration store data that it discovers at runtime (i.e. information that wasn't provided during tenant configuration). This is useful when your integration fetches data from a remote system (such as user IDs, warehouse locations, or sync cursors) and needs to persist it for future runs.

### How does Tenant Metadata Work?

Existing metadata is exposed to the integration code during a run as an environment variable. Metadata is updated at the end of every run from the last run stdout. If the last run stdout on a tenant with metadata on its integration release does not conform to the metadata schema, the run will fail.

### Adding Metadata to your Integration Release

To turn on the tenant metadata feature, make sure to add `metadata_schema` as a top level scope in Pandium.yaml. `metadata_schema` should have two keys: `schema` (required) and `uischema` (optional).<br>

* `schema` is a standard json schema. For more information, please see here - <https://json-schema.org/understanding-json-schema/reference>
* `uischema` is not currently used in Pandium. However, including it is useful if you are building your own UI on top of the metadata.

When adding metadata to your Pandium YAML, make sure to add this as a top level scope similar to your schema.

{% code expandable="true" %}

```yaml
metadata_schema:
  schema:
    name: metadata_schema
    properties:
      product_sync:
        type: boolean
      update_sync:
        type: boolean
      warehouse_location:
        type: string

  uischema:
    type: VerticalLayout
    elements:
      - title: Sync Products
        type: Label
      - label: Enable
        scope: '#/properties/product_sync'
        type: Control
      - title: Sync Updates
        type: Label
      - label: Enable
        scope: '#/properties/update_sync'
        type: Control
      - label: 'Warehouse Location'
        scope: '#/properties/warehouse_location'
        type: Control
```

{% endcode %}

### Updating Tenant Metadata at the End of a Run

Metadata is updated at the end of every run from the last run stdout. If the last run stdout on a tenant with metadata on its integration release does not conform to the metadata schema, the run will fail. At the end of every run, with the addition to writing stdout to the run, if there is a metadata schema available on the release, the Pandium platform will validate the entire stdout against the schema. Additional fields are allowed. If validation fails, the run will be marked as a failure with a special status, **Failed (Metadata Validation)** and the tenant's metadata will not be updated. If the schema passes validation, the metadata will be updated, not replaced.

Additional fields follow standard JSON Schema behavior. By default they are permitted unless the schema explicitly sets `additionalProperties: false`.

### Using Metadata to Populate Dynamic Configs

On manifest version 1.0, tenant metadata is the source for [dynamic configurations](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations) and [dependent selectors](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dependent-selector-configurations). A config field references a metadata key with `#/metadata/<key>`, and Pandium resolves the reference against the tenant's current metadata when it renders the Connection Settings page.

Each referenced key must be declared in `metadata_schema.schema.properties` and populated from a run's stdout. Because the form resolves these references when it renders, the options must already be in metadata before the form is shown. When a user connects a new tenant in the In-App Marketplace, Pandium runs an **init sync** (run mode `init`) before displaying the Connection Settings form — so an integration can populate option metadata in an init branch. Values that rarely change (for example, a list of channels or warehouses) can be fetched once during the init sync and left untouched on later syncs, rather than refetched on every run.

**Simple dynamic config** — reference the metadata key with `$ref`. If the metadata value is an array of strings it becomes an enum; an array of `{const, title}` objects becomes a `oneOf`.

```yaml
configs:
  schema:
    properties:
      colors:
        $ref: '#/metadata/available_colors'
metadata_schema:
  schema:
    type: object
    properties:
      available_colors:
        type: array
        items:
          type: string
```

**Dependent selector** — reference the parent options with `$ref` and the child map with `optionsMapPath`, and point `parentField` at the parent config.

```yaml
configs:
  schema:
    properties:
      food_type:
        $ref: '#/metadata/food_types'
      food_item:
        type: string
        optionsMapPath: '#/metadata/food_items_map'
        parentField: '#/properties/food_type'
```

Until a run has written metadata for a referenced key, the field is shown in an unsynced state and its options are disabled.

### Reading Tenant Metadata During a Run

Tenant metadata is written in JSON to a temporary file, the path which is exposed in a run as an environment variable. For example, in a Python integration, you could use the following code to read and log tenant metadata:

```
def main():
      tenant_metadata_file_path = os.getenv('PAN_CTX_TENANT_METADATA_FILE')
      if not tenant_metadata_file_path:
          logger.debug("no metadata file path set")
          return

      try:
          with open(tenant_metadata_file_path) as file:
              metadata = json.load(file)
          logger.debug(metadata)
      except FileNotFoundError:
          logger.debug("no metadata file found")
      except json.JSONDecodeError as e:
          logger.error(f"invalid metadata JSON: {e}")
```

### Updating & Reviewing Tenant Metadata Using the Pandium API

GET and PATCH endpoints are available for your team using the following links:<br>

* GET - <https://docs.pandium.com/reference/pandium-api#get-v2-tenants-tenant_id-metadata>
* PATCH - <https://docs.pandium.com/reference/pandium-api#patch-v2-tenants-tenant_id-metadata>

### Reviewing Tenant Metadata in the Pandium Integration Hub

If your tenant is on a release that is using tenant metadata, you can review the last stdout by doing the following steps:<br>

1. Pull up the tenant in question
2. Navigate to Details > Select Show next to Tenant Metadata\ <br>

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


# Environment Variables

Manage Pandium environment variables for context, configs, and secrets using PAN\_CTX, PAN\_CFG, and PAN\_SEC pas three types of environment variables that are presented to a script at run-time.

Environment variables within Pandium allow for the formatting of configs and secrets, and allow a simple way to manage and utilize various options in your integration. Typical environment variables will be prefixed with 'PAN\_CTX', which would be a normal environment variable stored within Context (StdOut), with 'PAN\_CFG' for configuration-related variables, and 'PAN\_SEC' for secret-related variables.

Here is an example of accessing the `PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT`environment variable in various languages.

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

```python
import os
os.environ['PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT']
```

{% endtab %}

{% tab title="JavaScript (Node)" %}

```
process.env.PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
ENV["PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT"]
```

{% endtab %}
{% endtabs %}

## Configs

These are the values that the user has entered into the connection settings form defined in the PANDIUM.yaml. These variables are prefixed with`PAN_CFG_`

If you have run a sync on a tenant, you can see how these variables are named and configured in a given log for a run.

## Secrets

Secrets are the information needed to securely access the APIs used by an integration. Depending on the type of authentication used, it might be an API Key, OAuth2 Token, etc., and are injected into the script environment prefixed by `PAN_SEC_`

These secrets can be accessed via your logs in the Integration Hub, and also seen by clicking the 'Show Secret Keys' button on a given connector within the Provisioning page - accessible through the Integration Detail page, which will show you a preview of what the connector secret format for that particular connector should look like.

<figure><img src="/files/2u6d3Kn1mOW6vXtJhAcJ" alt="" width="563"><figcaption><p>Example of NetSuite Secret Formats within the Integration Hub</p></figcaption></figure>


# Context: StdOut

Use Pandium context stdout variables (PAN\_CTX\_\*) to persist state, inspect run metadata, and power dynamic syncs between integration runs via JSON output.

The last thing written to the stdout file descriptor during a run is passed to the next run. Typically, this is used to pass state between runs using a JSON encoded string of an object that includes, for example, a list of IDs of resources that have already been processed or a timestamp of the most recently processed resource. The string passed to stdout is also used to power the data behind a [dynamic config](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations).

Below is an example of how to retrieve the contents of a context variable from the environment:

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

```python
import os
os.environ['PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT']
```

{% endtab %}

{% tab title="JavaScript (Node)" %}

```
process.env.PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
ENV["PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT"]
```

{% endtab %}
{% endtabs %}

### Context Variables

Below is a list of variables that are injected into the run environment with the `PAN_CTX` prefix.

<table><thead><tr><th width="336.3333333333333">name</th><th width="599">description</th><th width="236">notes</th></tr></thead><tbody><tr><td><strong>PAN_CTX_INTEGRATION_ID</strong></td><td>The Pandium ID of the Integration that is running currently.</td><td>an integer unique to your account</td></tr><tr><td><strong>PAN_CTX_INTEGRATION_NAME</strong></td><td>The name of the Integration that is running currently</td><td></td></tr><tr><td><strong>PAN_CTX_TENANT_ID</strong></td><td>The Pandium ID of the Tenant that is running currently.</td><td>An integer unique to your account</td></tr><tr><td><strong>PAN_CTX_TENANT_NAME</strong></td><td>The name of the Tenant that is running currently.</td><td></td></tr><tr><td><strong>PAN_CTX_RUN_MODE</strong></td><td>An enum of one of the following values: <code>init</code> , <code>normal</code>, <code>webhook</code></td><td></td></tr><tr><td><strong>PAN_CTX_RUN_TRIGGERS</strong></td><td>List of objects that contain the payload data for run triggers</td><td>Can view the mode, source, and payload data as well as headers it was sent with</td></tr><tr><td><strong>PAN_CTX_ENV</strong></td><td>The environment the sync is running in, i.e. dev/staging/prod</td><td></td></tr><tr><td><strong>PAN_CTX_LAST_RUN_START_TIME</strong></td><td>A datetime that represents the time the last run started. i.e. <code>2021-07-24 20:04:21 +0000 UTC</code></td><td>Will appear in environment only after first run.</td></tr><tr><td><strong>PAN_CTX_LAST_RUN_COMPLETION_TIME</strong></td><td>A datetime that represents the time the last run completed. i.e. <code>2021-07-24 20:07:21 +0000 UTC</code></td><td>Will appear in environment only after first run.</td></tr><tr><td><strong>PAN_CTX_LAST_RUN_PHASE</strong></td><td>An enum of one of the following values: <code>Succeeded</code>, <code>Failed (Platform Issue)</code>, <code>Failed (Integration Issue)</code> , <code>Failed (Timeout)</code></td><td>Will appear in environment only after first run.</td></tr><tr><td><strong>PAN_CTX_LAST_RUN_STD_OUT</strong></td><td>The JSON encoded string that was optionally printed to <em>stdout</em> during last Run.</td><td></td></tr><tr><td><strong>PAN_CTX_LAST_SUCCESSFUL_RUN_START_TIME</strong></td><td>A datetime that represents the time the last successful run. i.e. <code>2021-07-24 20:04:21 +0000 UTC</code></td><td>Will appear in environment only after <em>successful</em> first run.</td></tr><tr><td><strong>PAN_CTX_LAST_SUCCESSFUL_RUN_COMPLETION_TIME</strong></td><td>A datetime that represents the time the last successful run completed. i.e. <code>2021-07-24 20:07:21 +0000 UTC</code></td><td>Will appear in environment only after <em>successful</em> first run</td></tr><tr><td><strong>PAN_CTX_LAST_SUCCESSFUL_RUN_PHASE</strong></td><td>An enum of one of the following values: <code>Succeeded</code>, <code>Failed (Platform Issue)</code>, <code>Failed (Integration Issue)</code> , <code>Failed (Timeout)</code></td><td>Will appear in environment only after <em>successful</em> first run</td></tr><tr><td><strong>PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT</strong></td><td>The JSON encoded string that was optionally printed to <em>stdout</em> during last successful Run.</td><td></td></tr></tbody></table>

A good way to use the saved state between runs via stdout is to continue running large syncs sequentially in a 'dynamic sync' process.

Below are some examples of writing to stdout in various languages

{% tabs %}
{% tab title="C#" %}

```
Console.WriteLine("Hello World")
```

{% endtab %}

{% tab title="Java" %}

```java
System.out.println("Hello World!");
```

{% endtab %}

{% tab title="NodeJS" %}

```
console.log('Hello World')
```

{% endtab %}

{% tab title="PHP" %}

```php
echo "Hello World" . "\n"
```

{% endtab %}

{% tab title="Python" %}

```python
print("Hello World!")
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
puts "Hello Word!"
```

{% endtab %}
{% endtabs %}

{% hint style="danger" %} <mark style="color:red;">Please Note: When handling context/stdout, there is a 128kb limit that is supported within Pandium</mark>
{% endhint %}


# Logging (StdErr)

Log integration messages to stderr in Pandium so users can view run output and errors in the Integration Hub and In-App Marketplace across integrations and tenants.

All logs that you wish to display to your users should be written to `stderr.`

These logs can be viewed via the In-App Marketplace by users, or from within the Integration Hub on various pages, including the integration level, tenant level, and main run resource.

Below are various examples of how to do this is in multiple languages.

{% tabs %}
{% tab title="C#" %}

```
using System;

Console.Error.WriteLine("Hello World")
```

{% endtab %}

{% tab title="Java" %}

```java
System.err.println("Hello World!");
```

{% endtab %}

{% tab title="NodeJS" %}

```
console.error('Hello World')
```

{% endtab %}

{% tab title="PHP" %}

```php
fwrite(STDERR, 'Hello World' . PHP_EOL);
```

{% endtab %}

{% tab title="Python" %}

```python
import sys

print("Hello World!", file=sys.stderr)
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require "logger"

log = Logger.new(STDERR)

log.debug('Hello Word!')
```

{% endtab %}
{% endtabs %}


# Key Terminology

Understand core Pandium terms like Integration Hub, In-App Marketplace, connectors, tenants, runs, releases, and PANDIUM.yaml to navigate and manage the platform effectively.

### Integration Hub

The Pandium Integration Hub serves as the central administrative dashboard for your account. Manage various aspects, from integrations and tenants to partners and settings for other features—all from one place.

### In-App Marketplace

The In-App Marketplace is a white-labeled, highly customizable solution to showcase all of your integrations directly to end users. Designed to be displayed in an iframe within a web app that sits behind your company's login.

Explore the In-App Marketplace [here](https://docs.pandium.com/marketplaces/embedding-the-marketplace).

### Public Gallery

Pandium's Public Gallery is a highly customizable marketplace with advanced options, including lead generation forms, improved SEO, and additional white-labeling features. Build a comprehensive directory of your technology partners and seamlessly embed it on your marketing site.

Learn more about the Public Gallery [here](https://docs.pandium.com/marketplaces/public-gallery).

### Integration

Conceptually, an integration is a packaged-up application that syncs data between two or more systems. Whether its called applications or scripts, they can operate one-way or bi-directionally, scheduled or triggered differently. Integrations can also extend to one-to-many scenarios, i.e. if a customer is syncing multiple 3rd party accounts syncing to one of your accounts or vice versa.

With Pandium, think of the integration object as the platform's “parent” record for any application. At its core, it includes the essential content for listing a tile in an end user-facing marketplace. More often than not, it is a combination of all of the components needed to functionally sync data between systems powered by Pandium.

We currently support 2 types of Integrations:

* **Internal Integrations:**

These are functional apps which sync data between systems. The code that performs the syncs (ETL), determines end-user settings (Pandium.yaml) and all associated data are housed on the Pandium cloud. Marketing to marketplace content lives within internal integrations.

* **External Integrations:**

allow Pandium customers to highlight integrations or other listings which do not sync data on the Pandium platform. External integrations have marketing content and copy and marketplace activity is tracked by the platform, but there is no ETL running on Pandium. Customers may use external integrations to highlight legacy integrations, “coming soon” or beta (unpublished) apps or types of services that would not be powered by Pandium, like channel partners or agencies.

### Integration Detail

To access detailed integration information, navigate to the Integration Hub and proceed to the integration detail page through the Integrations sidebar resource.

For specific details related to integrations, you can also use the Pandium API.

### Connector

A connector serves as an integral component within an integration, allowing Pandium to securely provide the required encrypted secrets or tokens to the specific integration and tenant combination. Pandium's connectors are designed to accommodate the authentication requirements of partner systems, whether that involves an OAuth protocol, a simple API key, or a custom and proprietary method.

In contrast to many integration platforms, Pandium’s connectors focus solely on authorization, authentication, and receiving webhooks. It's important to note that Pandium's connectors refrain from interacting with external APIs beyond these essential functions. As such, there is no “Pandium version” or “wrapper” around third-party APIs.

### Connector Secrets

When an integration script runs on the platform, it dynamically accepts the necessary secrets as environment variables at runtime. These secrets are encrypted at rest and in transit, and cannot be exported.

For a given integration, connectors can be set on a per-tenant basis or globally. This global configuration is particularly useful when one facet of the integration remains static, such as utilizing a uniform SFTP or cloud storage bucket for all customer accounts. Opting for a global connector implies that authentication into the system is required only once. Subsequently, all created tenants will share the same set of global secrets, eliminating the need for redundant authentication.

Pandium connections can be diverse - we don't need to connect to a public API to work. Whether it's through public or private APIs (leveraging a direct AWS/GCP/Azure connection), SFTP, Storage Buckets, or even direct database connections, Pandium adapts seamlessly, provided the necessary access is available.

### Tenant

A tenant is an individual instance of an installed integration. At its core, it represents a distinctive collection of API keys or authentication tokens, coupled with the requisite configuration options essential for the seamless execution of a synchronization process. In most cases, tenants exhibit a one-to-one relationship with an account within your application that installs a specific integration.

### Multiple Tenants

There are instances where an end user, during the installation of an integration, may necessitate multiple tenants. This scenario arises when the end user possesses a single API key (or authentication token) for one system and multiple API keys (or authentication tokens) for other systems.

Example: You as a merchant have a NetSuite account, and each of your partners uses their account within NetSuite, each with their own unique credentials and permissions.

### Run

A run is both the execution of a script by Pandium, including its associated secrets and configurations, and the corresponding record of that synchronization for a specific tenant. Runs have associated logs, configured by the engineering user of the Pandium platform. These runs remain visible to both Pandium administrators and customers for a duration of up to 30 days. For extended historical insights, users can contact support for potential access beyond this timeframe.

Runs can be triggered via the Integration Hub, Marketplace, webhooks or the Pandium API.

### Release

A release is a version of the code that performs the ETL on Pandium in combination with the PANDIUM.yaml. New releases are automatically generated when the Source Control Integration detects changes in the specified repository, and they can also be manually initiated through the Integration Hub.

Releases offer the flexibility of being designated as default for all new installations. Tenants can be updated to new releases in bulk or individually. Releases may be tagged with identifying information to assist with version control.

### Release Channels

Release channels can be set specifically for integrations and tenants. The default release will be the version of the integration that customers automatically be provisioned upon installing the integration from your marketplace. There must be a default release selected for your customers to install an integration. To make a selection, one or more release versions must be built through Source Control.

There are three separate options for default release behavior:

1. Static Default: Installed tenants use the currently set default integration release. The release remains unchanged on the tenant unless manually reset within the administrator platform.
2. Dynamic Default: Installed tenants are created using the currently set default integration release. If there is a change in the integration’s default release changes, the installed tenants will change their release(s) to match on the next sync.
3. Latest: Tenants will always use the most recently-built release available for the integration.This ensures that tenants always operate with the latest release.

### Source Control

Pandium simplifies the management of Continuous Integration/Continuous Deployment (CI/CD) processes of your integrations through a feature known as Source Control. In the Pandium Integration Hub, you'll find a dedicated Source Control tab in the Settings sidebar resource where you can oversee and manage this process.

While Source Control is automatically deployed to your account, a setup process is necessary before beginning integration development. We currently support four different version control systems: Azure, Bitbucket, GitHub, and GitLab.

Source Control looks for the URL paths in your repository, as indicated in your integration configuration, and generates a new release based on changes in the code base.

Setting up this feature is key to hosting and managing integration on Pandium and should be the first step completed before embarking on integration development. As integrations are created, you'll be able to build releases for them on that integration detail page, or directly from the Source Control page in the future.

To learn more about setting up Source Control, check out the article [here](https://docs.pandium.com/integration-hub/setting-up-source-control).

### PANDIUM.yaml

For internal integrations with source code hosted in a remote repository, the PANDIUM.yaml is a file that should be included in the root level of the relevant directory. This file furnishes Pandium with essential information necessary to build and execute your integration script on our platform. Additionally, it defines the configuration options and user interface (UI) through the configs section, allowing your customers to input vital information crucial for the integration run.

\
Learn more specifics about the PANDIUM.yaml [here](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec).


# Pandium Integration Tutorial

Build your first Pandium integration by following a guided tutorial that walks you through creating and publishing a Slack-powered “Pokémon of the Day” app in under a day.

This tutorial will walk you through your first integration, and see how all the pieces of Pandium work together. During this process, you will touch a large portion of the Pandium platform and publish your first integration in less than a day.

In it, you will create a Pandium integration that sends a Slack message about a new Pokémon each day!

Check out Part 1, where you'll create a fully-functional integration from start to finish:

{% content-ref url="/pages/ysJeGqtiGy9kEcRNd4Bu" %}
[Pokémon of the Day, Part 1](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1)
{% endcontent-ref %}

Check out Part 2, where you'll learn how to configure more advanced options of an integration:

{% content-ref url="/pages/RZrnYC1w5wfpyIAf55rF" %}
[Pokémon of the Day, Part 2](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-2)
{% endcontent-ref %}


# Pokémon of the Day, Part 1

Build your first Pandium integration by connecting Slack, configuring tenants, and scheduling runs to send a unique “Pokémon of the Day” message without repeats.

Congratulations - The Pokémon Trainer Academy has hired you to develop integrations to run on the Pandium they just bought!

The Academy has noticed that many of their students confine themselves to just a few favorite Pokémon when it's better to train a wider variety. To address this they need you to write an integration that will send educational messages to the trainers about the many different Pokémon they could work with.

Here are the features the Academy wants in the finished integration:

* On a normal run it will identify & fetch the Pokémon of the day and then send a Slack message about it to the Academy's Pokémon trainers.
* The integration will not send a message about the same Pokémon twice.

Here's what you will get to do along the way:

* Create your first [integration](/getting-started/key-terminology#integration) on the Pandium Integration Hub.
* Connect your Pandium integration to a remote repository where you will keep its code.
* Create a [tenant](/getting-started/key-terminology#tenant) for the integration which will run your code.
* Connect that tenant to your Slack account.
* Fill out your integration's remote repository with your own TypeScript code.
* Make several [release](/getting-started/key-terminology#release)[s](/getting-started/key-terminology#release) for your integration with different versions of your code.
* Set a schedule for how often your tenant will run.
* Run the tenant so you get your Pokémon of the day in Slack!


# Create App in Slack to get Credentials

Create a Slack app and configure OAuth, redirect URLs, and bot permissions so you can securely use its client ID and secret to connect Slack to your Pandium integration.

We will use the Slack Web API for this integration, so you will need to obtain credentials for it. These will be used to provision your integration on Pandium.

1. Within [this Slack Quickstart Guide](https://api.slack.com/start/quickstart) and follow the steps for:
   * [ ] Creating an app.
   * [ ] Requesting scopes: for *Bot Token Scopes* choose **chat:write** and **users:read**.

There is no need to install or authorize your app through Slack because Pandium will do that when you connect a tenant.

2. On your Slack app's *OAuth & Permissions* page go to the *Redirect URLs* section and add <https://api.sandbox.pandium.com/v0/author/callback/oauth2>
3. On the *Basic Information* page go to the *App Credentials* section and make note of the *Client ID* and *Client Secret*. These will be needed to provision your integration on Pandium.
4. Confirm that your app has the necessary bot configuration by navigating to the App Home page in your Slack app settings.
5. On the App Home page, ensure your bot user is configured:
   * [ ] Find the Your App's Presence in Slack section and verify a bot user exists. If no bot user exists, make sure to double check scopes and a redirect URL were added as part of steps 1 & 2.
   * [ ] If you wish to modify your bot Display Name and Default username for the App's bot, click Edit next to "App Display Name".<br>

     <figure><img src="/files/jxwEBWA1dFLJ6L197f2V" alt="" width="504"><figcaption></figcaption></figure>

Now that you have Slack credentials we can use those to set up an integration on your Pandium Sandbox integration hub!


# Create Integration on the Pandium Integration Hub

Create and configure an internal “Pokémon of the Day” integration in the Pandium Integration Hub by connecting Slack OAuth, setting schedules, and bootstrapping your TypeScript repo.

Now you will create Pandium's record of the integration which will eventually be used to run your code. You'll also let Pandium generate the initial files for your new integration!

1. Use the command line to create a new branch on a remote repository where Pandium can put the starter files it will generate for your Pokémon integration:
   * [ ] This branch must be made on a repository which your [source control tenant](/reference/source-control) is authorized to access.
   * [ ] For example, run `git checkout -b pokemon-integration`.
   * [ ] Publish the new branch to the online repository (assuming the remote repository is named `origin`). Run `git push -u origin pokemon-integration`.
2. Log into your [sandbox Pandium Integration Hub.](https://admin.sandbox.pandium.com/)

<figure><img src="/files/RDsEUPGFRSXC4kiXw7lh" alt="" width="375"><figcaption></figcaption></figure>

3. From the sidebar menu choose **Integrations.** This will display the main *Integrations* page.

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

4. In the upper right corner click **Create.** This will display the *New Integration* dialog box.

<figure><img src="/files/3Wo3Br6jZwolrCA6anfx" alt="" width="375"><figcaption></figcaption></figure>

5. Choose **Internal** because your Pokémon integration will run on Pandium.
   * You can read more about the different kinds of integrations [here](/getting-started/key-terminology#integration).
6. Click **Create.** This will display the *Configure* tab for the *New Integration* page.

<figure><img src="/files/AASItAnBjm54pxulqhBE" alt="" width="341"><figcaption></figcaption></figure>

7. Under *Connectors* click the dropdown menu to open it.
8. Select **Slack (OAuth2)** from the list of Connectors.
   * Do not check the box for *Global* because this is not a Global connector (you can read more about the different kinds of connectors and their purposes [here](/getting-started/key-terminology#connector)).
   * You do not need to add a connector for the PokéAPI because you'll be able to fetch information from it without providing any auth.
9. Under *Details* enter an **Integration ID** and **Integration Name**.

<figure><img src="/files/m2m3kyEWgmXMHCcKHmfw" alt=""><figcaption><p>In this example the remote repository is at <a href="https://github.com/pandium/sample_integrations.git">https://github.com/pandium/sample_integrations.git</a>. In step 1 the pokemon-integration branch was added to that repository. The repository path tells Pandium to create a folder called POKEMON_OF_THE_DAY within that repository, and to add the starter files to that folder.</p></figcaption></figure>

10. Under *Remote Repository Settings,* do the following:
    * [ ] For *Repository Url,* enter the url for the online repository where you would like to store this integration's code. This should be the same one on which you just made a branch in step 1.
    * [ ] For *Repository Tracking Branch,* enter the name of the branch on which you will develop this integration (e.g. `pokemon-integration`).
    * [ ] For *Repository Path,* enter the path for a new folder in which you would like this integration to be made.
11. Under *Sync Schedule,* for *Default schedule for a new tenant* choose **Once Per Day** since the integration is called *Pokémon* ***of the day.***
12. Click **Create.** This will display the *Provision* tab.

<figure><img src="/files/Lug6bXq15Ek133l1H6bP" alt="" width="363"><figcaption></figcaption></figure>

13. Enter the **Client ID** and **Client Secret** you got when you created the Slack App.
14. In the dropdown menu for *Scope* select **users:read** and **chat:write**.
15. Leave *User Scope* blank.
16. Click **Provision**.

<figure><img src="/files/yJR7w7fHuZPf6sIW3MQk" alt="" width="372"><figcaption></figcaption></figure>

17. Click **Show Secret Keys** for the *Slack (OAuth2)* connector. Make note of PAN\_SEC\_SLACK\_OAUTH\_ACCESS\_TOKEN displayed in the dialog box. You will use this in your .env when you develop the integration locally.

<figure><img src="/files/wND9C7RWaEnxthwZh23l" alt="" width="375"><figcaption></figcaption></figure>

18. Click **Close** to exit the connector secret keys dialog box.

<figure><img src="/files/bej9lws6Q3vUE4CUWn35" alt="" width="372"><figcaption></figcaption></figure>

19. Click **Done**. This will display the integration details page.

The next step requires that your sandbox Integration Hub has its Source control tenant set up. If you haven't already done that, following [these instructions](/reference/source-control).

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

20. Click **Setup Integration Repo.** This will display the dialog box to A*dd Pandium files to your repo.*

<figure><img src="/files/tcrujw5nO9RZQcV3zqi9" alt="" width="244"><figcaption></figcaption></figure>

21. For *Language,* choose **Typescript**.
22. Click **Setup Repo.** This will display the *Source Control* tab for the *Admin Settings* page and kick off a run of your source control tenant. That run will generate a some files to jump start the process of writing the code for this integration.

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

23. Once that source control run is complete run `git pull origin pokemon-integration` to fetch the newly created files. They will help you start writing your integration!


# Make a Tenant

Create and connect a “Pokémon of the Day” tenant in the Pandium Integration Hub by linking Slack, choosing the Latest release channel, and configuring schedule and settings.

To run the integration on Pandium you will need a tenant. A tenant is an installed instance of the integration configured for a particular user.

When Pandium added those starter files to your integration's remote repository, it also built the initial [release](/getting-started/key-terminology#release) for your integration. That means you can now add your first tenant!

If you want more information you can review Pandium's documentation on [tenants](/getting-started/key-terminology#tenant) and [how to create ](/getting-started/key-terminology#tenant)them.

1. Navigate to your integration by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration. This will display the integration details page.

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

2. Click **Create Tenant**. This will display the *Create* tab of the *New Tenant* page.

<figure><img src="/files/mzP5zvueYPIF4T9eYuhj" alt="" width="285"><figcaption></figcaption></figure>

3. Under *Select Integration,* your Pokémon of the Day integration should already be selected, so leave it as is.
4. Under *Version, choose* **Channel.**
5. Open the dropdown for *Integration Release Channel* and select **Latest**. This ensures that your tenant will be updated to any new releases automatically.
6. Enter a **Name** for this tenant.
7. Click **Create.** This will display the *Connect* tab.

<figure><img src="/files/6oJ01XlJd8wWI6uN4cRb" alt="" width="284"><figcaption></figcaption></figure>

8. Click **Connect** for the Slack connector. This will open the *Tenant Secrets* dialog box, which will display an authorization link.

<figure><img src="/files/65naOmdPmhmVQ9feEHCU" alt="" width="299"><figcaption></figcaption></figure>

9. Click the authorization link in the *Tenant Secrets* dialog box. This will open the Slack App installation window.

<figure><img src="/files/7A9XgB3QVUZxIh759iE7" alt="" width="309"><figcaption></figcaption></figure>

10. Click **Allow**. This will close the Slack App installation window, so you should return to the *New Tenant* page of your Pandium Sandbox Integration Hub.
11. To close the Tenant Secrets dialog box to click **Back**.
12. Click **Next**. This will display the *Configure* tab.

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

At this point, the configuration options from the starter files' PANDIUM.yaml are shown, and they aren't relevant to your project. You will customize this when you write the code.

13. Click **Next.** This will display the *Schedule* tab.

<figure><img src="/files/BNFeJ5LJ79e0FzCxM7qh" alt="" width="228"><figcaption></figcaption></figure>

The obvious schedule for this project is once per day, but during development it doesn't make sense to have it running on a schedule.

14. In the *Schedule* dropdown, select **paused**.
15. Click **Save**. This will display the *Tenant Detail* page for your newly created tenant.

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

Congratulations! You have created and connected your first Pandium tenant!

If you'd like, you can click **Sync Now** to kick off a manual run. But there isn't much point to that at the moment because you haven't written any code! The next step is to fill out those starter files Pandium put in your remote repository with logic for your integration.


# Write the Integration in Typescript

Write a TypeScript “Pokémon of the Day” integration for Pandium by customizing the starter repo, updating package settings, installing dependencies, and building before each run.

Beginning with the starter files created by Pandium, you will now write your Pokémon of the day integration and get it working locally!

Since you just pulled Pandium's initial commit your new integration folder should have a the following file structure:

<pre><code><strong>├── src
</strong>  └── index.ts
  └── lib.ts
└── package.json
└── PANDIUM.yaml
└── tsconfig.json
</code></pre>

Start by replacing the pre-filled generic information with something that fits our project.

1. In the package.json change the name from `test-integration` to `pokemon-of-the-day`.

You may notice the start command is `node/build/src` and doesn't mention `ts-node`. That's because this TypeScript integration is a module. You can read more about [TypeScript Modules](https://www.typescriptlang.org/docs/handbook/modules/introduction.html) if you haven't written one before, but for our integration's purposes it means:

* You will need to do `run npm build` before any code changes you've made will take effect.
* Your imports will need to reference .js files.

1. Run `npm install && npm run build`. This should add the node\_modules and build folders to your root directory.


# Add the .env

Configure a .env for your Pandium TypeScript integration by storing Slack OAuth tokens, run mode, and PAN\_SEC secrets locally while keeping them ignored from version control.

Pandium will give your integration its secrets, tenant settings, and context when it runs on Pandium. You will use a .env to give that information to your integration when it is run locally.

You can read more about the [environmental variables](/getting-started/anatomy-of-an-integration/environment-variables) Pandium gives your integration during run time.

1. Add a .env file to the root folder of your integration, so your file structure should look like this:

```
  ├── build 
  ├── node_modules 
  ├── src     
  │   ├── index.ts
  │   └── lib.ts
  ├── .env
  ├── package.json
  ├── PANDIUM.yaml
  └── tsconfig.json
```

2. Add this new .env to your .gitignore. This ensures that the secrets kept there will not be published to a remote repository.

When you connected your tenant on Pandium it installed the Slack App to your Slack workspace. This generated an OAuth access token, which Pandium has saved so your integration can access it when you run the tenant. You need to put that access token in your .env, so let's find its value.

3. Go to <https://api.slack.com/apps>.
4. Under *Your Apps,* click on your Pokémon of the Day app. This is the one you created to get the client ID and client secret to provision your integration's Slack connector.
5. On your Slack App's *OAuth & Permissions* page find the *OAuth Tokens for your Workspace* section. A *Bot User OAuth Token* should now be displayed because it was generated when you connected your tenant.

When you created the Pandium record of your integration on the Integration Hub, you clicked **Show Secret Keys** for the Slack connector and discovered a secret called PAN\_SEC\_SLACK\_OAUTH\_ACCESS\_TOKEN.

6. Add the following line to your .env file, replacing the value with your Slack App's *Bot User OAuth Token*.

```properties
PAN_SEC_SLACK_OAUTH_ACCESS_TOKEN = xoxb-....
```

Part of the context for a Pandium integration is run mode; it can be `normal` or `init`.

7. Add the following line to your .env file:

```properties
PAN_CTX_RUN_MODE = normal
```

8. To check that your integration is reading from your .env correctly run `npm run build && npm run start`. Since the package.json's start script is `node build/src/`, you should see something like this logged:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config {}
------------------------SECRET------------------------
Secret {
  slack_oauth_access_token: 'xoxb-...'
}
------------------------CONTEXT------------------------
Context { run_mode: 'normal' }
------------------------ENV----------------------------
{...}
```

The run mode and slack token from the .env have been successfully read!

One difference from what is displayed above is that the secret will be printed in full. Such a log is useful to confirm that you can access that secret from the .env. However it should not be kept because you don't want to accidentally push code that publishes a user's secret.

9. In the src/index.ts remove the lines where the secrets are logged.

You may have noticed that src/index.ts uses `console.error` for it logs rather than `console.log`. When writing an integration to be run on Pandium, `console.log` should be reserved for [the standard out](/getting-started/anatomy-of-an-integration/environment-variables/stdout), and all other logs should be written to [`stderr`](/getting-started/anatomy-of-an-integration/environment-variables/stderr).

Another difference from what is displayed above is that the environment object will be quite large. It's useful to see what that object gives you access to, but for this integration we will only be using the environmental variables from our .env file.

10. In the src/index.ts remove the lines where the whole environment object is logged.

The Pokémon Trainer Academy hasn't requested any configs for this integration. That's why we haven't added any `PAN_CFG` variables to the .env yet, so it makes sense that the `config` object is still empty.

Let's update the *Connections Settings* page for the integration to remove the irrelevant configuration options that were displayed when you created your first tenant.


# Configure the PANDIUM.yaml

Customize the connection settings page so it doesn't display options that aren't relevant to the Pokémon Trainer Academy's requests.

One of the [PANDIUM.yaml](https://github.com/pandium/external-docs/blob/master/build/anatomy-of-an-integration/pandium.yaml-spec)'s key purposes is to populate the *Connections Settings* page which allows users to customize how their [tenant](/getting-started/key-terminology#tenant) will run.

Right now the Pokémon Trainer Academy hasn't asked for any tenant settings, so we just need to remove the unnecessary configurations.

1. Remove `configs` and everything under it from the the PANDIUM.yaml.

Your PANDIUM.yaml should now look like the one [here](https://github.com/pandium/sample_integrations/blob/510749317b86510450afdaec14aa153e47cc7675/POKEMON_OF_THE_DAY/PANDIUM.yaml).

Let's take a look at how the connection settings page looks with this updated PANDIUM.yaml!


# Check the Customized Connection Settings Page

Customize and verify your Pandium connection settings by building a new release, updating the tenant on the Latest channel, and confirming the cleaned-up settings page.

You just customized the PANDIUM.yaml, which controls the Connection Settings page. Let's take a look at that updated page for your tenant!

1. Commit your code and push it to your remote repository.
2. On the Integration Hub navigate to your integration by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration. This will display the integration details page.

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

3. Click **Build Release**. This will open the *Manually build a new Release* dialog box.

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

4. Enter a tag for the new release; this should be something to remind you what features have been added in this release.
   * If the source control tenant is currently running the **Build Release** button will be disabled. It will be enabled when the source control run is complete.
5. Click **Build Release.**
   * This will display the *Source Control* tab of the *Admin Settings* page and kick off a run of your [source control](/reference/source-control) tenant.
   * That run will generate a new release of your integration based on the code you just pushed to your remote repository.
6. When the source control run is complete, navigate back to your integration's detail page by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration.
7. Click the **Tenants** tab. This will display a list of your integration's tenants. Right now there should be one.

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

8. Click on your tenant. This will display the *Tenant Detail* page.

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

9. Click **Settings**. This will display the *Connection Settings* page. Since your release channel is *Latest* the tenant has been automatically updated to the new release you just made.

<figure><img src="/files/05b1hYWMsE1vhZgqnwls" alt="" width="375"><figcaption></figcaption></figure>

Now, the connections setting page doesn't have any unnecessary options!


# Add the Pokémon client

Fetch Pokémon data in your Pandium TypeScript integration by installing a Pokédex client, initializing it in code, and calling it to retrieve a test Pokémon before wiring real logic.

A client is a class with methods that allow you to interact a particular API, e.g. making asynchronous requests to fetch and post records. You will import the PokéAPI's client from a library.

At the moment the code doesn't have any Pokémon information to work with, so let's set up a Pokémon client that will fetch our first Pokémon of the day!

1. Run `npm install pokedex-promise-v2 --save`.

The [docs for this library](https://www.npmjs.com/package/pokedex-promise-v2?activeTab=readme#install-) show how to create an instance of a Pokémon client.

2. In `src/index.ts` import `Pokedex` from the Pokémon library, and create an instance of the Pokémon client. Do this within the `run` function so that your code will be able to use it when it runs.
3. Use the [`getPokemonByName` method ](https://www.npmjs.com/package/pokedex-promise-v2?activeTab=readme#pokemon)from your instance of the Pokémon client to fetch a Pokémon by name .

At this point your src/index.ts should look like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config()
import Pokedex from 'pokedex-promise-v2'
import { Config, Secret, Context } from './lib.js'

const run = async () => {
    const context = new Context()
    const secrets = new Secret()
    const config = new Config()

    console.error(`This run is in mode: ${context['run_mode']}`)
    console.error('------------------------CONFIG------------------------')
    console.error(config)

    console.error('------------------------CONTEXT------------------------')
    console.error(context)

    const pokeClient = new Pokedex()

    const tyranitar = await pokeClient.getPokemonByName('tyranitar')
    console.error(tyranitar)
}

run().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

4. Run `npm run build && npm run start`.

You should see something like this logged - except that the printed object should include the whole `tyranitar` object.

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context { run_mode: 'normal' }
{
  abilities: [
    { ability: [Object], is_hidden: false, slot: 1 },
    { ability: [Object], is_hidden: true, slot: 3 }
  ],
  ... further properties of the Pokémon Tyranitar
}
```

This shows the Pokémon client works, so you can remove the logic for fetching and logging out `tyranitar`.


# Add the Slack Client

Connect Slack to your Pandium TypeScript integration by installing the Slack Web API client, authenticating with your bot token, and listing workspace users to target future messages.

Now you will import the Slack client from the library @slack/web-api and confirm you can use it to fetch information from Slack.

A Slack client will let us post our daily messages, so let's get our code talking to Slack.

1. Run `npm install @slack/web-api`.

These [docs for this library](https://www.npmjs.com/package/@slack/web-api) show how to create an instance of a Slack client.

2. In `src/index.ts` do the following:
   * [ ] Import the `WebClient` from the Slack library.
   * [ ] Access the Slack token from the secrets object.
   * [ ] Use that token to create an instance of the Slack client. Do this within the `run` function so that your code will be able to use it when it runs.

These [Slack web API docs](https://api.slack.com/methods/users.list) describe the `users.list` endpoint. That will be helpful because you'll need some information about the users in the Pokémon Trainer Academy's Slack workspace in order to send them messages.

3. Use your instance of the Slack client to fetch a list of users in the workspace that your Slack token has access to.

At this point your src/index.ts should look like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config()
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { Config, Secret, Context } from './lib.js'

const run = async () => {
    const context = new Context()
    const secrets = new Secret()
    const config = new Config()

    console.error(`This run is in mode: ${context['run_mode']}`)
    console.error('------------------------CONFIG------------------------')
    console.error(config)

    console.error('------------------------CONTEXT------------------------')
    console.error(context)

    const pokeClient = new Pokedex()
    const slackClient = new WebClient(secrets.slack_oauth_access_token)

    const { members } = await slackClient.users.list()
    console.error(members)
}

run().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

4. Run `npm run build && npm run start`.

You should see something like this logged - except that the printed array should be filled with users from your Slack workspace.

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context { run_mode: 'normal' }
[
  {
    id: 'USLACKBOT',
    team_id: 'TCF1SSXMJ',
    name: 'slackbot',
    deleted: false,
    color: '757575',
    real_name: 'Slackbot',
    tz: 'America/Los_Angeles',
    tz_label: 'Pacific Standard Time',
    tz_offset: -28800,
    profile: {
      title: '',
      phone: '',
      skype: '',
      real_name: 'Slackbot',
      real_name_normalized: 'Slackbot',
      display_name: 'Slackbot',
      display_name_normalized: 'Slackbot',
      fields: {},
      status_text: '',
      status_emoji: '',
      status_emoji_display_info: [],
      status_expiration: 0,
      avatar_hash: 'sv41d8cd98f0',
      always_active: true,
      first_name: 'slackbot',
      last_name: '',
      image_24: 'https://a.slack-edge.com/80588/img/slackbot_24.png',
      image_32: 'https://a.slack-edge.com/80588/img/slackbot_32.png',
      image_48: 'https://a.slack-edge.com/80588/img/slackbot_48.png',
      image_72: 'https://a.slack-edge.com/80588/img/slackbot_72.png',
      image_192: 'https://a.slack-edge.com/80588/marketing/img/avatars/slackbot/avatar-slackbot.png',
      image_512: 'https://a.slack-edge.com/80588/img/slackbot_512.png',
      status_text_canonical: '',
      team: 'TCF1SSXMJ'
    },
    is_admin: false,
    is_owner: false,
    is_primary_owner: false,
    is_restricted: false,
    is_ultra_restricted: false,
    is_bot: false,
    is_app_user: false,
    updated: 0,
    is_email_confirmed: false,
    who_can_share_contact_card: 'EVERYONE'
  },
  ...
  ]
```

Review documentation about the Slack Web API to develop a strategy for sending the Pokémon of the Day Slack messages. For example, you'll need to use [the postMessage endpoint](https://api.slack.com/methods/chat.postMessage), and the docs say "If you want your app's bot user to start a 1:1 conversation with another user in a workspace, provide the user's user ID as the `channel` value"

That means you'll need the Slack member IDs of the Pokémon trainers who the Academy said should receive the daily messages. Consult the list of Slack members which was logged in the last run, and take note of those ID's (if you don't happen to see any Pokémon trainers you should at least be able to find yourself and note your own ID 😉)

Now that you know the Slack client works, so you can remove the logic for fetching and logging out the Slack users from src/index.ts.


# Add the pokemonSync flow

Send unique daily Pokémon updates from your Pandium integration by adding a pokemonSync flow that fetches Pokémon, posts Slack messages, and advances IDs using saved stdout state.

You have clients that talk to both APIs, so put them together to start sending educational Pokémon messages via Slack.

A Pandium integration can be run in normal mode or init mode. The `pokemonSync` flow is for normal mode.

The goal in this flow is to send a Slack message about a new Pokémon each day. To do this we will need to:

* [ ] Read which Pokémon was selected in the most recently run- to ensure the Pokémon in this run is a new one.
* [ ] Fetch the Pokémon for this run.
* [ ] Send a Slack message about that Pokémon to the Academy's students.

1. Add the file for this new flow:

   * [ ] Within src add a folder called processLogic.
   * [ ] Within src/processLogic add pokemonSync.ts.

   Your file structure should now look like this:

```
  ├── build 
  ├── node_modules 
  ├── src
  │  ├── processLogic
  │  │  └── pokemonSync.ts
  │  ├── index.ts
  │  └── lib.ts
  ├── .env
  ├── package.json
  ├── PANDIUM.yaml
  └── tsconfig.json
```

2. Within src/processLogic/pokemonSync.ts add the shell of an asynchronous `pokemonSync` function.

```typescript
export const pokemonSync = async () => {
    console.error('------------------------POKEMON SYNC------------------------')
}
```

3. Within src/index.ts import `pokemonSync` and invoke it within the `run` function when the run mode is normal.

The src/index.ts should now look something like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config()
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { Config, Secret, Context } from './lib.js'
import { pokemonSync } from './processLogic/pokemonSync.js'

const run = async () => {
    const context = new Context()
    const secrets = new Secret()
    const config = new Config()

    console.error(`This run is in mode: ${context['run_mode']}`)
    console.error('------------------------CONFIG------------------------')
    console.error(config)

    console.error('------------------------CONTEXT------------------------')
    console.error(context)

    const pokeClient = new Pokedex()
    const slackClient = new WebClient(secrets.slack_token)
   
    if (context.run_mode === 'normal') {
        await pokemonSync()
    } 
}

run().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

4. Run `npm run build && npm run start`, and you should see the following logged:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context { run_mode: 'normal' }
------------------------POKEMON SYNC------------------------
```

5. Fetch a Pokémon by doing the following:
   * [ ] In src/index.ts pass the Pokémon Client to `pokemonSync`.
   * [ ] Add `pokeClient` as an argument to `pokemonSync`.
   * [ ] Import `Pokedex` to src/processLogic/pokemonSync.ts to define the type of the `pokeClient`.
   * [ ] Declare a variable `nextPokemonId`. For now just set it to `247`.
   * [ ] Pass `nextPokemonId` to the `pokeClient.getPokemonByName`method to fetch a Pokémon by ID, because [the docs for the Pokémon library](https://www.npmjs.com/package/pokedex-promise-v2) state "Any function with the designation 'ByName' can also be passed an integer ID."
   * [ ] Log out the results of that fetch.

The pokemonSync.ts file should look something like this:

```typescript
import Pokedex from 'pokedex-promise-v2'

export const pokemonSync = async (
    pokeClient: Pokedex
) => {
    console.error('------------------------POKEMON SYNC------------------------')
    const nextPokemonId = 247
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)
    console.error(pokemonOfTheDay)
}
```

6. Run `npm run build && npm run start`.

You should see the same information logged as before - except that now a large Pokémon object has also been printed. This confirms the Pokémon client within `pokemonSync` is working, so you can remove the `console.error(pokemonOfTheDay)`.

7. Add a function to transform the `pokemonOfTheDay` into a Slack message.
   * [ ] Within src add the file transformations.ts.
   * [ ] Within transformations.ts define and export the function `pokemonToSlackMessage`.
   * [ ] Review [these Slack Web API docs ](https://api.slack.com/methods/chat.postMessage)for the postMessage endpoint and the `Pokemon` Typescript interface to fill out the `pokemonToSlackMessage` transformation function.

Here is one way transformations.ts could look:

```typescript
import { Pokemon } from 'pokedex-promise-v2'
import { ChatPostMessageArguments } from '@slack/web-api'

export const pokemonToSlackMessage = (
    pokemon: Pokemon,
    channel: string
): ChatPostMessageArguments => {
    const abilities = pokemon.abilities
        .map((ability) => ability.ability.name)
        .join(', ')
    const text = `The Pokemon of the Day is *${pokemon.name}*!
        *Abilties:* ${abilities}
        *Base Experience:* ${pokemon.base_experience}
        *Height:* ${pokemon.height}
        *Weight:* ${pokemon.weight}`
    const message: ChatPostMessageArguments = {
        channel: channel,
        text: text,
        blocks: [
            {
                type: 'section',
                text: {
                    type: 'mrkdwn',
                    text: text,
                },
            },
        ],
    }

    if (pokemon.sprites.back_default) {
        message.blocks?.unshift({
            type: 'image',
            image_url: pokemon.sprites.back_default,
            alt_text: `${pokemon.name} sprite`,
        })
    }
    return message
}
```

8. Within `pokemonSync` use `pokemonToSlackMessage` and `slackClient.chat.postMessage` to send a message to each of the Academy's Pokémon trainers.
   * [ ] Add `slackClient` as an argument of `pokemonSync` and import `WebClient` from the Slack library to define its type.
   * [ ] Within src/index.ts pass `slackClient` to `pokemonSync`.
   * [ ] Import `pokemonToSlackMessage` to pokemonSync.ts.
   * [ ] Within `pokemonSync` declare an array called `slackMemberIds`. Eventually it will hold the IDs of all the Academy's Pokémon trainers, but for development purposes just put your own Slack ID in there.
   * [ ] Pass each element of `slackMemberIds` to `pokemonToSlackMessage` to create a `slackMessage`.
   * [ ] Pass each `slackMessage` to `slackClient.chat.postMessage`.

The pokemonSync.ts file should look something like this:

```typescript
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from "@slack/web-api"
import {pokemonToSlackMessage} from '../transformations.js'

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient
) => {
    console.error('------------------------POKEMON SYNC------------------------')
    const nextPokemonId = 247
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds){
        const slackMessage = pokemonToSlackMessage(
            pokemonOfTheDay,
            slackID
        )
        await slackClient.chat.postMessage(slackMessage)
    }
}
```

9. Run `npm run build && npm run start`. You should get a Slack message about the Pokémon of the Day!

We're not quite done though. If you try running `npm run start` again you will get another Slack message about the same Pokémon. One of the Academy's requests is that we won't repeat Pokémon.

To accomplish this goal, we will use context to learn which Pokémon have already been used.

Pandium stores the [standard out](/getting-started/anatomy-of-an-integration/environment-variables/stdout) of each tenant's last successful normal sync, so it can be accessed as an [environmental context variable](/getting-started/anatomy-of-an-integration/environment-variables#context) during the next run.

10. Alter the integration so that it prints a standard out during a normal sync.
    * [ ] `pokemonSync` should return a standard out which should be `{last_pokemon_id: nextPokemonId }`.
    * [ ] In src/index.ts print a stringified version of the standard out returned by `pokemonSync`.
11. Run `npm run build && npm run start`.

You should get another slack message about that same Pokémon. However the logs now include the standard out, which should look something like this `{"last_pokemon_id":247}`.

12. Add a standard out to your .env.

```properties
PAN_CTX_LAST_SUCCESSFUL_RUN_STD_OUT= '{"last_pokemon_id":247}'
```

13. Use that standard out in `pokemonSync` to ensure the Pokémon of the day is not repeated.
    * [ ] In src/index.ts pass `context` to `pokemonSync`.
    * [ ] In pokemonSync.ts add the argument `context` to `pokemonSync` and import the Typescript interface `Context` to define the type for that new argument.
    * [ ] In `pokemonSync` add the variable `lastPokemonId`. Its value should be accessed from `context.last_successful_run_std_out`.
    * [ ] In `pokemonSync` change `nextPokemonId` so that it will be the next number after `lastPokemonId`.

Your pokemonSync.ts should look like this:

```typescript
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from "@slack/web-api"
import {pokemonToSlackMessage} from '../transformations.js'
import { Context } from '../lib.js'

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient,
    context: Context
) => {
    console.error('------------------------POKEMON SYNC------------------------')
    
    let lastPokemonId = 0
    if (context.last_successful_run_std_out) {
        const lastStdOut = JSON.parse(context.last_successful_run_std_out)
        lastPokemonId = Number(lastStdOut.last_pokemon_id) || 0
    }
    
    const nextPokemonId = lastPokemonId + 1
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds){
        const slackMessage = pokemonToSlackMessage(
            pokemonOfTheDay,
            slackID
        )
        await slackClient.chat.postMessage(slackMessage)
    }

    return {last_pokemon_id: nextPokemonId}
}
```

14. Run `npm run build && npm run start`. You should get another Slack message about a **new** Pokémon!

![](/files/QZJvK3oQ1MZ2k34hhcbS)

The logs should look like this:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context {
  run_mode: 'normal',
  last_successful_run_std_out: '{"last_pokemon_id":247}'
}
------------------------POKEMON SYNC------------------------
{"last_pokemon_id":248}
```

Notice the following about the logs:

* The context now has a the last run's standard out.
* The ID printed to the standard out for this run is greater than the ID from the last run's standard out.

Your pokemonSync.ts should loook like the one [here](https://github.com/pandium/sample_integrations/blob/510749317b86510450afdaec14aa153e47cc7675/POKEMON_OF_THE_DAY/src/processLogic/pokemonSync.ts). In fact, all your integration files should match all the ones in [this repository at this commit](https://github.com/pandium/sample_integrations/tree/510749317b86510450afdaec14aa153e47cc7675/POKEMON_OF_THE_DAY).

The final step is to see what this all looks like when it is run on Pandium!


# Run Normal Sync on the Tenant

Trigger and verify a normal sync for your Pandium tenant to send Slack “Pokémon of the Day” messages and confirm stdout-based state is passed between successive runs.

Before you can try out your new normal sync you will need to put your tenant on a new release based on your new code. To do that follow all the steps in the [Check the Customized Connection Settings Page](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/check-the-customized-connection-settings-page).

1. Once your tenant is on that new release, navigate to your integration's tenant by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration > the **Tenants** tab > the row for your tenant.

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

2. Click the arrow on **Sync Now** > **Normal Sync.** This will kick off a normal sync for your tenant, which will appear in the ***Runs*** list at the bottom of the page. Before the run is complete you should get a Slack message about the Pokémon of the Day.

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

3. Click the icon in the run's *Status* column. This will display the *Run Detail* page for the run.

The log there should look just like the logs for the normal syncs you ran locally during development (with the exception that the context logged out has far more information now).

The last line of the log should be the standard out and it should look something like this:

```
[OUT] {"last_pokemon_id":"1"}
```

4. Navigate back to the *Tenant Detail* page by clicking the tenant's name at the top of the *Run Detail* page.

To confirm that context is correctly being passed from one run to the next, start another normal sync.

5. Click the arrow on **Sync Now** > **Normal Sync.** Before the run is complete you should get a Slack message about a different Pokémon of the day.
6. Locate the run in the *Run* list at the bottom of the *Tenant Detail* page, and click on the icon in the run's *Status* column. This will display the *Run Detail* page for the run.

The log in the Run Detail page should be very similar to the last run but take note of a few things:

* Within the context logged at the start of the run you should be able to find `last_successful_run_std_out: '{"last_pokemon_id":1}'`. Notice that this matches the standard out the tenant's last normal sync.
* The last line of the log should be the standard out , and its ID should be greater than the last one `[OUT] {"last_pokemon_id":2}`.

Now that you've seen your code runs correctly on a Pandium tenant you just need to change the tenant's schedule so it runs on a daily basis!


# Update the Tenant Schedule

Schedule your “Pokémon of the Day” Pandium tenant to run once per day so it automatically sends daily Slack messages to the configured trainer IDs without manual syncs.

Now that you're done developing your integration update your tenant to run on a daily basis!

1. Navigate to your integration's tenant by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration > the **Tenants** tab > the row for your tenant.

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

2. Click **Sync Schedule**. This will display the *Sync Schedule* page.
3. In the schedule drop down menu select **Once Per Day***.*
4. Click **Save Changes.**

Now your integration will run on Pandium and send a message to each ID in the `slackMemberIds` array about a new Pokémon every day. This is everything the Pokémon Trainer Academy asked you to build!


# Pokémon of the Day, Part 2

Enhance your “Pokémon of the Day” Pandium integration so each tenant picks a trainer and preferred Pokémon type via dynamic configs powered by Slack users and live PokéAPI data.

Assuming you have already completed Part 1 of this Tutorial, you can now expand your Pokémon of the day Integration by adding dynamic configurations with options populated by an init sync flow.

The students at the Pokémon Trainer Academy love using your integration so much that they've requested new features! Instead of having all the students receive the same Pokémon message, each student wants the ability to choose which type of Pokémon they will learn about.

Here is how you will accomplish this:

* Each Pokémon trainer will have their own integration tenant, and they will tell the Academy's integration user experience administrator their preferred connection settings.
* For each tenant the Academy's UX admin will be able to select one Pokémon trainer as the recipient for the daily Slack Message: the integration will dynamically populate those options by reading available users from the Slack workspace.
* For each tenant the Academy's UX admin will be able to select one Pokémon type -the one chosen by the tenant's trainer: the integration will dynamically populate those options by reading the different Pokémon types from the PokéAPI, ensuring the list remains updated even with new additions.

Here's what you will get to do along the way:

* Add an[ init sync flow](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations#integration-script-init-mode).
* Add [dynamic configurations](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations).
* Make new [release](/getting-started/key-terminology#release)[s](/getting-started/key-terminology#release) for this integration.

You should already have the following from Part 1 of this Tutorial:

* [A Pokémon of the Day Slack App](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/create-app-in-slack-to-get-credentials) that provided the client ID and client secret for your Slack connector.
* [A Pokémon of the Day integration ](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/create-integration-on-the-pandium-integration-hub)on your Pandium sandbox integration hub.
* [A connected tenant](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/make-a-tenant) for that integration which created a Slack *Bot User OAuth Token* to use in your .env during local development.

These will all be used as you build these new features, so you can dive right into writing the code!


# Update the PANDIUM.yaml

Update the PANDIUM.yaml for “Pokémon of the Day” to add dynamic pokemon\_type and slack\_user configs using schema definitions and UISchema so tenants can customize messages.

This is where you will start to set up the functionality that allows a user to choose which type of Pokémon they will learn about and who will receive the daily Slack message.

One of the [PANDIUM.yaml](https://github.com/pandium/external-docs/blob/master/build/anatomy-of-an-integration/pandium.yaml-spec)'s key purposes is to populate the *Connections Settings* page. That page allows users to customize how their [tenant](/getting-started/key-terminology#tenant) will run.

At the moment the PANDIUM.yaml in your Pokémon of the Day integration does not have any configurations, but the Academy has asked for these dynamic configs to be added: `pokemon_type` and `slack_user`

1. Add `configs` with its `schema` and `uischema` to your PANDIUM.yaml, so it looks like this:

```yaml
version: 0.4
base: node:20.9.0
build: npm install --production && npm i --save-dev @types/node && npm run build
run: node .
configs:
  schema:
    required:
    definitions:
    properties:
  uischema:
    elements:
```

2. Under `schema` `properties` add the following:

```yaml
      pokemon_type:
        type: string
        $ref: '#/definitions/pokemon_types'
      slack_user:
        type: string
        $ref: '#/definitions/slack_users'
```

The values in the `$ref` for each of these properties determine the options for that config. E.g. the `pokemon_type` config will have a dropdown menu on the *Connections Settings* page, and it will be filled with whatever is in `#/definitions/pokemon_types`.

So let's add the definitions for `pokemon_types` and `slack_users`!

3. Make the `schema` `definitions` look like this:

```yaml
    definitions:
      slack_users:
        type: number
        oneOf:
          - title: Placeholder
            const: placeholder
      pokemon_types:
        enum:
            - placeholder  
```

When you built the first iteration of the integration you saw that Pandium saves the standard out of a successful normal sync and passes it to the next run through context. Pandium also stores the standard out of a successful init sync.

When you add the init sync flow you will end it by printing a standard out which will look something like this:

```
{
   "pokemon_types":[
      {"const":"1","title":"normal"},
      {"const":"2","title":"fighting"},
      ... other pokemon types fetched from the PokéAPI
   ],
   "slack_users":[
      {"const":"UCEGPFQRX","title":"Jeff"},
      {"const":"UCEMD4QCQ","title":"Juanita"},
      ... other Slack users fetched from your Slack workspace.
   ]
}
```

Pandium will save that init sync's standard out and read it when displaying the *Connections Settings* page. It will replace each `placeholder` with the content of the lists from your init sync's standard out.

You can read more about dynamic configs [here](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations).

4. Under `schema` `required` add `pokemon_type` and `slack_user`.
5. Add this `Section` element to the `uischema` `elements`:

```yaml

    - type: Section
      label: Configure Pokémon of the Day
      hintText: Select which type of Pokémon you would like to learn about, and the Slack User who will receive the Pokémon of the day message.
      elements:

      - label: Pokémon Type
        scope: '#/properties/pokemon_type'
        type: Control

      - label: Slack User
        scope: '#/properties/slack_user'
        type: Control

```

Your PANDIUM.yaml should now look like the one [here](https://github.com/pandium/sample_integrations/blob/master/POKEMON_OF_THE_DAY/PANDIUM.yaml).

Let's take a look at how the connection settings page looks with this updated PANDIUM.yaml!


# Check the Updated Connection Settings Page

Preview your updated Pandium connection settings by building a new release, updating the tenant on the Latest channel, and confirming the new dynamic configs appear with placeholders.

You just added new elements to the PANDIUM.yaml, which controls the Connection Settings page. Let's take a look at that those configs for your tenant!

Before you can see the results of your updated PANDIUM.yaml on the *Connection Settings* page you will need to put your tenant on a new release based on your new code.

1. Follow all the steps in [Check the Customized Connection Settings Page](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/check-the-customized-connection-settings-page).

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

4. When you click **Settings** for your tenant the *Connection Settings* page will be displayed. Since your release channel is *Latest* the tenant has been automatically updated to the new release you just made.

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

The new configurations are present! But the only options we have are placeholders. Those will be replaced with proper options fetched from each API during the init sync.

So the next step is to write that int sync flow!


# Add Dynamic Configs

Power dynamic configs for “Pokémon of the Day” by adding an initSync flow that outputs Slack user and Pokémon type options to stdout, populating tenant dropdowns automatically.

Your updated Connections Settings page has the new configs you need. Now we're going to write an init sync flow to populate options for each of those configs.

A Pandium integration can be run in normal mode or init mode. The initSync flow is for the init mode.

The goal in this initSync flow is to print a standard out with data that will populate the options for our two [dynamic configurations:](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations)

* The Slack user to receive the Pokémon of the day message.
* The type of Pokémon allowed for Pokémon of the day.

To do this we will need to:

* [ ] Fetch the users from Slack and the Pokémon types from PokéAPI.
* [ ] Reformat each Slack user to be in the form expected for a OneOf Option.
* [ ] Print a standard out that lists all the options for `pokemon_types` and `slack_users`.

1. Within src/processLogic add initSync.ts.

Now your file structure should now look like this:

```
  ├── build 
  ├── node_modules 
  ├── src
  │  ├── processLogic
  │  │  ├── initSync.ts
  │  │  └── pokemonSync.ts
  │  ├── index.ts
  │  ├──  lib.ts
  │  └── transformations.ts 
  ├── .env
  ├── package.json
  ├── PANDIUM.yaml
  └── tsconfig.json
```

2. Within src/processLogic/initSync.ts add the shell of an asynchronous `initSync` function.

```typescript
export const initSync = async () => {
    console.error('------------------------INIT SYNC------------------------')
}
```

3. Within src/index.ts import `initSync` and invoke it within the `run` function when the run mode is init.

The src/index.ts should look something like this:

```typescript
import * as dotenv from 'dotenv'
dotenv.config()
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { Config, Secret, Context } from './lib.js'
import { pokemonSync } from './processLogic/pokemonSync.js'
import { initSync } from './processLogic/initSync.js'

const run = async () => {
    const context = new Context()
    const secrets = new Secret()
    const config = new Config()

    console.error(`This run is in mode: ${context['run_mode']}`)
    console.error('------------------------CONFIG------------------------')
    console.error(config)

    console.error('------------------------CONTEXT------------------------')
    console.error(context)

    const pokeClient = new Pokedex()
    const slackClient = new WebClient(secrets.slack_oauth_access_token)

    if (context.run_mode === 'normal') {
        const standardOut = await pokemonSync(pokeClient, slackClient, context)
        console.log(JSON.stringify(standardOut))
    } else {
        await initSync()
    }
}

run().then(
    () => {},
    () => {
        process.exitCode = 1
    }
)
```

4. In the .env update the run mode:

```properties
PAN_CTX_RUN_MODE= init
```

5. Run `npm run build && npm run start`.

You should see the following logged, which shows that `initSync` is running:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: init
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context { 
   run_mode: 'init',
   last_successful_run_std_out: '{"last_pokemon_id":247}'
}
------------------------INIT SYNC------------------------
```

6. Fetch the Slack users by doing the following:
   * [ ] In src/index.ts pass the `slackClient` to `initSync`.
   * [ ] In src/processLogic/initSync.ts add `slackClient` as an argument to `initSync`.
   * [ ] In src/processLogic/initSync.ts import the `WebClient` from the Slack library and use it to define the type of the `slackClient`.
   * [ ] In `initSync` use `slackClient.users.list` to fetch slack users.
   * [ ] Log out the results of that fetch.

The initSync.ts file should look something like this:

```typescript
import { WebClient } from '@slack/web-api'

export const initSync = async (slackClient: WebClient) => {
    console.error('------------------------INIT SYNC------------------------')
    try {
        const response = await slackClient.users.list()
        console.error(response.members)
    } catch (error) {
        console.error(error)
    }
}
```

7. Run `npm run build && npm run start`.

You should see the same information logged as before - except that now an array of Slack members from your workspace has also been printed. This confirms the Slack client within `initSync` is working.

You may recall from the work on the PANDIUM.yaml that the options printed to the initSync standard out for your `slack_user` config should only have the properties `const` and `title`. This means the elements of the Slack members array will need to be transformed to the proper `const` and `title` format.

8. Create a Typescript interface for `OneOfOption`.
   * [ ] Add models.ts to the src folder.
   * [ ] Within src/sharedModels.ts define `OneOfOption`.
   * [ ] Import `OneOfOption` to src/processLogic/initSync.ts.

```typescript
export interface OneOfOption {
    const: string
    title: string
}
```

9. Print the slack user options to the standard out.
   * [ ] When looping through the members list reformat any active and non bot user to be a `OneOfOption`. Then add it to a list of `slackUsers`.
   * [ ] Make `initSync` return a standard out object that includes the `slack_users` list.
   * [ ] In src/index.ts use `console.log` to print a stringified version of that standard out object.

The initSync.ts should now look like this:

```typescript
import { WebClient } from '@slack/web-api'
import { OneOfOption } from '../models.js'

export const initSync = async (slackClient: WebClient) => {
    console.error('------------------------INIT SYNC------------------------')

    const slackUsers: OneOfOption[] = []
    try {
        const response = await slackClient.users.list()

        response.members?.forEach((user) => {
            if (
                user.deleted ||
                user.is_bot ||
                !user.is_email_confirmed ||
                !user.id ||
                !user.name
            )
                return

            slackUsers.push({
                const: user.id,
                title: user.name,
            })
        })
    } catch (error) {
        console.error(error)
        return {}
    }
    return {
        slack_users: slackUsers
    }
}
```

10. Run `npm run build && npm run start`. The logs should look something like this:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: init
------------------------CONFIG------------------------
Config {}
------------------------CONTEXT------------------------
Context { 
   run_mode: 'init',
   last_successful_run_std_out: '{"last_pokemon_id":247}'
}
------------------------INIT SYNC------------------------
{"slack_users":[{"const":"UCEGPFQRX","title":"Jeff"},{"const":"UCEMD4QCX","title":"Juanita"},... other Slack users fetched from your Slack workspace.]}
```

During the work on the PANDIUM.yaml this is exactly what we'd said needed to be printed to the init sync standard out to populate options for the `slack_user` config.

Now do the same for the Pokémon types!

11. Fetch the Pokémon types by doing the following:
    * [ ] In src/index.ts pass the `pokeClient` to `initSync`.
    * [ ] In src/processLogic/initSync.ts add `pokeClient` as an argument to `initSync`.
    * [ ] Import the `Pokedex` to initSync.ts and use it to define the `pokeClient` type.
    * [ ] In `intSync` use `pokeClient.getTypesList` to fetch Pokémon types.
    * [ ] Log out the results of that fetch.

The initSync.ts file should look something like this:

```typescript
import { WebClient } from '@slack/web-api'
import Pokedex from 'pokedex-promise-v2'
import { OneOfOption } from '../models.js'

export const initSync = async (    
        pokeClient: Pokedex,
        slackClient: WebClient) => {
    console.error('------------------------INIT SYNC------------------------')
    
    try {
        const { results: types } = await pokeClient.getTypesList()
        console.error(types)
    } catch (error) {
        console.error(error)
    }

    const slackUsers: OneOfOption[] = []
    try {
        const response = await slackClient.users.list()

        response.members?.forEach((user) => {
            if (
                user.deleted ||
                user.is_bot ||
                !user.is_email_confirmed ||
                !user.id ||
                !user.name
            )
                return

            slackUsers.push({
                const: user.id,
                title: user.name,
            })
        })
    } catch (error) {
        console.error(error)
        return {}
    }
    return {
        slack_users: slackUsers
    }
}
```

12. Run `npm run build && npm run start`. You should see the same information logged as before - except that there should now also be an array of Pokémon types. This confirms the `PokeClient` within `initSync` is working.
13. Loop through each of the Pokémon types list and add its name to a list of `pokemonTypes`. Then add the `pokemon_types` to the standard out object returned by `initSync`.
14. Run `npm run build && npm run start`. The logs should look something like this:

    ```
    > pokemon-of-the-day@1.0.0 start
    > node build/src/

    This run is in mode: init
    ------------------------CONFIG------------------------
    Config {}
    ------------------------CONTEXT------------------------
    Context { run_mode: 'init' }
    ------------------------INIT SYNC------------------------
    {"slack_users":[{"const":"UCEGPFQRX","title":"Jeff"},{"const":"UCEMD4QCX","title":"Juanita"},... other slack users fetched from your Slack workspace.],"pokemon_types":["normal","fighting","flying","poison","ground","rock","bug","ghost","steel","fire","water","grass","electric","psychic","ice","dragon","dark","fairy","unknown","shadow"]}
    ```

    The standard out of the `initSync` now lists options for both the `slack_user` and `pokemon_type` configs.

The initSync.ts should now look like [this](https://github.com/pandium/sample_integrations/blob/master/POKEMON_OF_THE_DAY/src/processLogic/initSync.ts).

Let's take a look at how this standard out of this new init mode affects the options on the tenant setting page!


# Run Init Sync on the Tenant

Run an init sync on your Pandium tenant to replace placeholder options with live Slack users and Pokémon types, then save settings and trigger a normal sync to test messages.

The init sync prints a standard out that lists options for both of the dynamic configurations. Let's run an init sync for the tenant and take a look at those options in the Connection Settings page!

Before you can try out your new init sync flow, you will need to put your tenant on a release based on your new code. To do that follow all the steps in [Check the Customized Connection Settings Page](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/check-the-customized-connection-settings-page).

Once your tenant is on that new release, you might take a look at its *Connections Settings* page. It still says *Placeholder* for the two dynamic configs. This is because we haven't yet run the new init sync for this tenant.

1. Navigate to your integration's tenant by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration > the **Tenants** tab > the row for your tenant.

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

2. Click the arrow on **Sync Now** > **Init Sync.** This will kick off an init sync for your tenant, which will appear in the *Runs* list at the bottom of the page.

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

If you're curious you can click the icon in the run's *Status* column. This will display the *Run Detail* page for the run. The log there should look just like the logs for the init syncs you ran locally during development (with the exception that the context logged out has far more information now).

You can navigate back to the *Tenant Detail* page by clicking the tenant's name at the top of the *Run Detail* page.

3. Once the init sync run has completed click **Settings**. This will display the *Connection Settings* page for the tenant.

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

*Placeholder* is no longer an option for either the Pokémon Type config or the Slack User config.

4. Choose one of the Pokémon types fetched from the PokéAPI.
5. Choose yourself as the person who will receive the Pokémon message of the day.\
   Selecting yourself is just for development purposes.\
   When the Academy's UX admin sets up a tenant for each Pokémon trainer they will select the appropriate trainer and their requested Pokémon type.
6. Click **Save Changes.** This will close the *Connections Settings* page to display the *Tenant Detail* pag&#x65;*.*

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

7. Click the arrow on **Sync Now** > **Normal Sync.** This will kick off a normal sync for your tenant, which will appear in the *Runs* list at the bottom of the page. Before the run is complete you should get a Slack message about the Pokémon of the Day.

You may have noticed that the Pokémon in that message was just the one with the next ID, and not necessarily of the type you just selected. This is because `pokemonSync` isn't accessing these new configurations.

<figure><img src="/files/5v8N89zpAH9cNB3WxjH3" alt=""><figcaption></figcaption></figure>

8. Click the icon in the run's *Status* column. This will display the *Run Detail* page for the run.

At the top of the logs you should see the config object now has the new configs!

```
------------------------CONFIG------------------------
Config {
  pokemon_type: 'rock',
  slack_user: '<YOUR-SLACK-MEMBER-ID>',
}
```

Let's update `pokemonSync` so it makes use of those new configs!


# Update the pokemonSync flow

Update the pokemonSync flow so each tenant’s daily Slack message uses the selected Pokémon type and Slack user from dynamic configs, while still advancing IDs via saved stdout state.

You have access to the newly created slack\_user and pokemon\_type configs. Let's update pokemonSync to make use of them.

The Pokémon Trainer Academy needs the new `pokemonSync` to do the following:

* The Pokémon of the day must always be of the type selected in the tenant's connection settings.
* The Slack message should only be sent to the Slack user specified in the tenant's connection settings.

To do this we will need to:

* [ ] Fetch Pokémon Type selected in the config.
* [ ] Select the Pokémon of the Day from the Pokémon within that type.
* [ ] Fetch the Pokémon of the Day.
* [ ] Send a Slack message about that Pokémon to the Slack user selected in the config.

1. In the .env update the run mode and enter selections for the two dynamic configs:

```properties
PAN_CTX_RUN_MODE= normal
PAN_CFG_POKEMON_TYPE = rock
PAN_CFG_SLACK_USER= <YOUR-SLACK-MEMBER-ID>
```

In the config object of your tenant's most recent normal sync logs you should have seen your Slack user ID. Replace the Slack user ID above with your own.

2. Fetch the selected Pokémon type by doing the following:
   * [ ] In src/index.ts pass the `config` to `pokemonSync`.
   * [ ] In src/processLogic/pokemonSync.ts add `config` as an argument to `pokemonSync` and import `Config` to define the type of this new argument.
   * [ ] Pass `config.pokemon_type` to the `pokeClient.getTypeByName`method to fetch the selected type.
   * [ ] Log out the results of that fetch.

The pokemonSync.ts file should look something like this:

```typescript
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from '@slack/web-api'
import { pokemonToSlackMessage } from '../transformations.js'
import { Context, Config } from '../lib.js'

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient,
    context: Context,
    config: Config
) => {
    console.error(
        '------------------------POKEMON SYNC------------------------'
    )

    const pokemonType = await pokeClient.getTypeByName(config.pokemon_type)
    console.error(pokemonType)

    let lastPokemonId = 0
    if (context.last_successful_run_std_out) {
        const lastStdOut = JSON.parse(context.last_successful_run_std_out)
        lastPokemonId = Number(lastStdOut.last_pokemon_id) || 0
    }

    const nextPokemonId = lastPokemonId + 1
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds) {
        const slackMessage = pokemonToSlackMessage(pokemonOfTheDay, slackID)
        await slackClient.chat.postMessage(slackMessage)
    }

    return { last_pokemon_id: nextPokemonId }
}
```

3. Run `npm run build && npm run start`.

You should get a Pokémon Slack message, but we're more interested in examining the logs; they should look something like this:

```
> pokemon-of-the-day@1.0.0 start
> node build/src/

This run is in mode: normal
------------------------CONFIG------------------------
Config { pokemon_type: 'rock', slack_user: '<YOUR-SLACK-MEMBER-ID>' }
------------------------CONTEXT------------------------
Context {
  run_mode: 'normal',
  last_successful_run_std_out: '{"last_pokemon_id":247}'
}
------------------------POKEMON SYNC------------------------
{
  damage_relations: {... various damage relations properties of the rock Pokémon type}
  ... more properties of the rock Pokémon type
}
{"last_pokemon_id":248}
```

You can remove the `console.error(pokemonType)`

You can learn about the shape of a Pokémon type by examining the one you just logged out or reviewing [the PokéAPI docs for the `type` endpoint](https://pokeapi.co/docs/v2#types).

You'll see that each Pokémon type has the property `pokemon`; it's an array of the type's Pokémon. We should select the Pokémon of the day from that list!

Each member of the `pokemon` array has the following shape:

```json
{
  "slot": 1,
  "pokemon": {
    "name": "sandshrew",
    "url": "https://pokeapi.co/api/v2/pokemon/27/"
  }
}
```

4. Change how `nextPokemonId` is defined; it should be the ID of the first member of the fetched type's `pokemon` array whose ID is greater than `lastPokemonId`.

Here is one way you could do this:

```typescript
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from '@slack/web-api'
import { pokemonToSlackMessage } from '../transformations.js'
import { Context, Config } from '../lib.js'

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient,
    context: Context,
    config: Config
) => {
    console.error(
        '------------------------POKEMON SYNC------------------------'
    )

    const pokemonType = await pokeClient.getTypeByName(config.pokemon_type)
    const pokemonOptions = pokemonType.pokemon

    let lastPokemonId = 0
    if (context.last_successful_run_std_out) {
        const lastStdOut = JSON.parse(context.last_successful_run_std_out)
        lastPokemonId = Number(lastStdOut.last_pokemon_id) || 0
    }

    let nextPokemonId: string | undefined
    for (const pokemon of pokemonOptions) {
        const pokemonId = Number(pokemon.pokemon.url.split('/').slice(-2, -1)[0])
        if (pokemonId <= lastPokemonId) continue
        nextPokemonId = String(pokemonId)
        break
    }
    if(!nextPokemonId) return {last_pokemon_id: lastPokemonId }
    
    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMemberIds = ['<YOUR-SLACK-MEMBER-ID>']

    for (const slackID of slackMemberIds) {
        const slackMessage = pokemonToSlackMessage(pokemonOfTheDay, slackID)
        await slackClient.chat.postMessage(slackMessage)
    }

    return { last_pokemon_id: nextPokemonId }
}

```

5. Run `npm run build && npm run start`.

Unless you changed the standard out in your .env this run's results may not look too different from the last run's (except the logs won't include the Pokémon type). This is because the list of Pokémon within a type often has many Pokémon of consecutive IDs . Regardless, you have shown that the new method of defining `nextPokemonId` does not interfere with the rest of the flow's functionality.

6. Now adjust pokemonSync to use the slack\_user config
   * [ ] Remove the array of hard coded `slackMemberIds`.
   * [ ] Instead of sending a Slack message for every member in that array just pass `config.slack_user` to `pokemonToSlackMessage`, and only call `slackClient.chat.postMessage` for that one `slackMessage.`

Your pokemonSync.ts could look like this now:

```typescript
import Pokedex from 'pokedex-promise-v2'
import { WebClient } from '@slack/web-api'
import { pokemonToSlackMessage } from '../transformations.js'
import { Context, Config } from '../lib.js'

export const pokemonSync = async (
    pokeClient: Pokedex,
    slackClient: WebClient,
    context: Context,
    config: Config
) => {
    console.error(
        '------------------------POKEMON SYNC------------------------'
    )

    const pokemonType = await pokeClient.getTypeByName(config.pokemon_type)
    const pokemonOptions = pokemonType.pokemon

    let lastPokemonId = 0
    if (context.last_successful_run_std_out) {
        const lastStdOut = JSON.parse(context.last_successful_run_std_out)
        lastPokemonId = Number(lastStdOut.last_pokemon_id) || 0
    }

    let nextPokemonId: string | undefined
    for (const pokemon of pokemonOptions) {
        const pokemonId = Number(pokemon.pokemon.url.split('/').slice(-2, -1)[0])
        if (pokemonId <= lastPokemonId) continue
        nextPokemonId = String(pokemonId)
        break
    }
    if(!nextPokemonId) return {last_pokemon_id: lastPokemonId }

    const pokemonOfTheDay = await pokeClient.getPokemonByName(nextPokemonId)

    const slackMessage = pokemonToSlackMessage(pokemonOfTheDay, config.slack_user)
    await slackClient.chat.postMessage(slackMessage)
    
    return { last_pokemon_id: nextPokemonId }
}

```

7. Run `npm run build && npm run start`.

Once again, this run's results may not look too different from the last run's. This shows that accessing the slack User's ID from `config`, instead of a hardcoded list does not interfere with the rest of the flow's functionality.

Your pokemonSync.ts should match [this one](https://github.com/pandium/sample_integrations/blob/master/POKEMON_OF_THE_DAY/src/processLogic/pokemonSync.ts). In fact your integration files should match all the ones in [this repository](https://github.com/pandium/sample_integrations/tree/master/POKEMON_OF_THE_DAY).

The final step is to see what this all looks like when it is run on Pandium!


# Run updated Normal Sync on the Tenant

Test your updated normal sync by running the tenant so it sends a Slack “Pokémon of the Day” message to the configured user, filtered by the Pokémon type set in Connection Settings.

Your pokemonSync flow uses the new configs to determine the Pokémon of the Day and its recipient. Let's run a normal sync for the tenant to ensure it all works correctly.

Before you can try out your new normal sync you will need to put your tenant on a new release based on your new code. To do that follow all the steps in [Check the Customized Connection Settings Page](/getting-started/pandium-integration-tutorial/pokemon-of-the-day-part-1/write-the-integration-in-typescript/check-the-customized-connection-settings-page).

1. Once your tenant is on that new release, navigate to your integration's tenant by clicking **Integrations** from the side bar > the **Unpublished** tab > the card for your *Pokémon of the Day* integration > the **Tenants** tab > the row for your tenant.

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

2. Click the arrow on **Sync Now** > **Normal Sync.** This will kick off a normal sync for your tenant.

Before the run is complete you should get a Slack message because you are the user selected in the *Connections Settings.* The Pokémon of the Day in the message should be of the type selected in the *Connections Settings.*

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

With this enhancement the Pokémon Trainer Academy's integration user experience administrator can start setting up tenants customized to each of their trainer's Pokémon education needs!

Great work!


# Pandium Integration Development Kit (IDK)

Pandium’s Integration Development Kit (IDK) auto-generates flexible, production-ready integrations with project scaffolding, API clients, a local CLI, and an AI-powered integration generator.

Leverage a suite of tools that generate ready-to-run Pandium integrations for you automatically. Maintain 100% flexibility and customization with low-level access to integration code.

## Features

### Project Scaffolding

Instantly set up your software project in the language of your choice directly in your connected repository.

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

### API Clients

A software library for interacting with 3rd Party APIs, including IDE IntelliSense Support, pagination, rate limiting, and secret management.

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

### Pandium CLI

Use a simple binary to access Pandium directly from your machine for local troubleshooting, secret access, and local runs.

{% embed url="<https://www.youtube.com/watch?ab_channel=Pandium&v=IHyBsvXsLdU>" %}

### AI Integration Generator

LLM-powered engine that generates a working Pandium Integration with one click. \~90% of a working integration with your specified systems, running on Pandium within minutes.

{% embed url="<https://youtu.be/5Lz2D2f9EhU?si=nc24DMndur-GSWa8>" %}

<br>


# Pandium Clients

Get out-of-the-box Pandium API clients that handle pagination, retries, rate limiting, and IDE hints so you can quickly build B2B SaaS integrations without custom plumbing.

Pandium's Clients, fill a gap in the market by offering B2B SaaS organizations out-of-the-box plumbing for **connecting directly with APIs** to build and launch integrations.

## Why Use the Pandium Clients?

1. Pandium has [hundreds of pre-built connectors](/connectors/connectors-101) to choose from.
2. Our API clients seamlessly interacts with APIs. They take care of paging, automatic retries, and rate limiting - all ensuring optimized throughput.
3. Our API clients also provide context-aware code insights on the APIs being integrated. This **eliminates the need to constantly reference API docs** while coding an integration’s business logic.

## How to Access the Pandium Clients

1. Pull your code from your remote repository down onto your local machine to start working in your IDE with the client in the integration.
2. You will see an example of how to instantiate the client. If you included code generation, you will see the client passed to a flow. Within the flow, the client instance will have been used to either fetch or post something to the API.\
   \
   If you did not include code generation, you will see the client used to fetch some records.

## How to Use a Pandium Client

1. Once a client has been added to your project, import your client to any file in the project using

{% code title="Import Format" %}

```markup
import [Client Name] from '@pandium/[connector]-client'
```

{% endcode %}

{% code title="Importing ShipBob Client" %}

```
import ShipbobClient from '@pandium/shipbob-client'
```

{% endcode %}

How to import additional models

<pre data-title="Example"><code><strong>import { CreateTicketModel } from '@pandium/shipbob-client'
</strong></code></pre>

## Example of using client

```typescript
import ShipbobClient from '@pandium/shipbob-client'


try {
    const sbClient = new ShipbobClient()
    console.error('Fetching and logging out some ShipBob channels')
    let recordCounter = 0
    
    // List and print 10 channels from api 
    const channels = await sbClient.listChannels()
    for (const record of channels) {
        if (recordCounter > 10) break
        console.error(record.id)
        recordCounter++
    }
} catch (error) {
    console.error(':x: Unexpected ShipBob error.')
    console.error(error)
}
```


# Integration Code Generator: AI Powered

Generate integrations in minutes with Pandium’s AI-powered code generator, which scaffolds flows, PANDIUM.yaml configs, and API client usage from your selected objects.

AI code generation excels when treated as a specialized tool within a well-architected system. By combining constrained AI capabilities with strong engineering practices and human oversight, development teams can achieve significant acceleration in integration development while maintaining production-grade reliability. With this in mind, we've created the Pandium Integration Code Generator, using a combination of carefully crafted prompts and our API Clients. Now, with only selecting the key objects you would like mapped in your flows, you can generate a basic integration in minutes.

## How to Use the Integration Code Generator

1. [Create an internal integration](https://docs.pandium.com/~/revisions/x7Thkx1S9I9SnLbXNFwg/integration-hub/pandium-quick-start/getting-started-with-creating-an-integration) within the Pandium Integration Hub.\
   \
   \&#xNAN;*For repository branch, you can enter a new branch name, which Pandium will automatically create upon code generation.*
2. Select the 'Start Repo' button

<figure><img src="/files/1qghph4T0BRUpsI96UlB" alt=""><figcaption></figcaption></figure>

3. Review the repository path for your PANDIUM.yaml and select TypeScript as your language.\
   \
   \&#xNAN;*While TypeScript is the only language currently supported with the Integration Code Generator, more will be coming soon!*

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

5. Select both "Include API Clients" & "Include CodeGen" to trigger the flow mapper.

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

5. Select the objects you would like to sync from each system for each flow. You can change the direction of the flow by toggling the middle arrow.

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

6. Select START to kick off the code generation. You will be automatically redirected to our source control page. Wait a moment for the build to kick off. You can follow along with the build process in the logs.

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

7. Navigate to your repository and see your new code, in the branch you selected.

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

## What's included in the code generation?

1. Your [PANDIUM.yaml, ](broken://spaces/FLZtvq4ESMETvRuO77jh)which defines your custom user configuration.

<figure><img src="/files/0D6BRpcLn0R1CE1parEP" alt=""><figcaption></figcaption></figure>

2. Your custom flows logic

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

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

3. The use of the [Pandium Clients ](https://app.gitbook.com/o/-MfEyg17bPwE_6Fs6Y-q/s/-MfJn-9R_dn6dvcGNcdk/~/changes/307/getting-started/pandium-development-kit/pandium-clients)for your selected connectors

At this point, source control will have made a release for your integration based on this code. You can use that release to [create and connect a tenant](/integration-hub/creating-a-tenant).

The Pandium CLI can then be used to access the secrets and configs for that tenant in local development with the command:

```
pandium local build && pandium local run [tenant-id]
```

See a demo of an integration being created between Gorgias and Iterable using the Integration Code Generator:

{% embed url="<https://youtu.be/5Lz2D2f9EhU?si=bePXM2cNtzqcFjmg>" %}


# Pandium CLI

This is a command-line interface that allows developers using Pandium to build, test, and manage integrations directly from their command line while leveraging Pandium’s infrastructure.

## Notes

* This tool is currently only available for Mac OS users.
* The Pandium CLI will only interface with internal integrations running on the Pandium platform.
* To use this tool, you will need to create an integration and tenant in the Pandium Integration Hub, with fully provisioned connectors.
* Running an integration locally in init mode will not update the dynamic configs available to this tenant on the Integration Hub.

## Installation

The terminal command to download the Pandium CLI can be accessed directly from the Pandium Admin Dashboard.

1. Log into the Pandium Admin Dashboard
2. Navigate to Settings > Developer Resources

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

3. Copy the command for your system (E.g. MacOS aarch64 (Apple Silicon))
   1. If you don't see a command listed for your platform of choice, please let us know.

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

4. Paste the command into your terminal and hit “Enter.”
5. Follow the documentation below to start using the CLI and run commands based on your needs.

## Authenticate

To list and manage your integrations, authenticate using the following command:

```
pandium login [environment]
```

If no environment is provided, it will default to sandbox. Possible environment values currently are *sandbox*, *sandbox-eu*, *demo*

## Usage

Get a list of all available commands:

```
pandium help
```

You will get the below output:

```
Usage:pandium login [environment]
   	pandium logout
   	pandium get integrations [integration_id]
   	pandium get tenants [OPTIONS] [tenant_id]
   	pandium get help [COMMAND]
   	pandium local build
   	pandium local run [OPTIONS] <tenant_id>
   	pandium local help [COMMAND]
   	pandium help [COMMAND]...

Options:
  -h, --help 	Print help
  -V, --version  Print version

pandium login:
Log in to Pandium! Takes environment name as an option, defaults to sandbox
  -h, --help     	Print help
  [environment]  [default: sandbox] [possible values: sandbox, sandbox-eu, demo]

pandium logout:
End your Pandium session
  -h, --help  Print help

pandium get:
Get internal integrations or their tenants
  -h, --help  Print help

pandium get integrations:
See a list of all your internal integrations
  -h, --help        	Print help
  [integration_id]

pandium get tenants:
See a list of tenant associated with the provided integration id
  -i, --integration-id <integration-id>
  	--include-active-secrets
  -h, --help                         	Print help
  [tenant_id]

pandium get help:
Print this message or the help of the given subcommand(s)

pandium local:
Execute commands from your local PANDIUM file (PANDIUM.yaml, PANDIUM.json, or PANDIUM.toml)
  -h, --help  Print help

pandium local build:
Execute the 'build' command in your PANDIUM.yaml
  -h, --help  Print help

pandium local run:
Run the integration found in the current folder using the env values from the provided tenant id. Local .env file will override any values saved in pandium
  -m, --mode <mode>  Optionally specify a run mode. Defaults to 'normal' [default: normal] [possible values: init, normal]
  -h, --help     	Print help
  <tenant_id>

pandium local help:
Print this message or the help of the given subcommand(s)

pandium help:
Print this message or the help of the given subcommand(s)
  [COMMAND]...  Print help for the subcommand(s)
```


# Setting Up Source Control

Set up Pandium Source Control to manage CI/CD for integrations by connecting GitHub, GitLab, Bitbucket, or Azure repos, auto-building releases, and injecting secure build environment secrets.

### **What is Source Control?**

Pandium enables easy management of the CI/CD of your integrations through a process labeled Source Control. Within the Pandium Integration Hub, you'll see a Source Control tab in the Settings sidebar resource where this process can be managed.

Source Control will be automatically deployed to your account, but requires further setup before beginning integration development. We currently support four different version control systems: [Azure](/reference/source-control/azure), [Bitbucket](/reference/source-control/bitbucket), [GitHub](/reference/source-control/github) and [GitLab](/reference/source-control/gitlab).

Source Control looks for the URL paths in your repository denoted in your integration configuration and will build a new release based on changes in the code base.

Setting up this feature is key to hosting and managing integration on Pandium, and should be the first step completed before it begins. As integrations are created, you'll be able to build releases for them on that integration detail page, or directly from this page in the future.

### **Setting Up Source Control**

1. Navigate to the Source Control tab within the Settings resource on the sidebar.
2. Connect to the preferred repository by hitting 'Connect' next to the repository name.
3. Complete the authentication process for the relevant repository, and that's it!
   1. Please note: when connecting your repository to Pandium for the first time, you’ll need to authorize Pandium with read and write permissions in order to properly access and modify relevant files within your repo. Below is an example of these steps when using GitHub:
      1. Step 1 - Select "Request" for Organization Access
      2. Step 2 - Once step 1 is completed, then select "Authorize Pandium". <mark style="color:red;">Please note, if you perform Step 2 before hitting "Request", then this will result in all Source Control runs failing.</mark><br>

         <figure><img src="/files/Q2eXH4e6aYCjvs4CnWuh" alt="" width="563"><figcaption></figcaption></figure>
   2. For more information related to repository permissions, please see [here](https://docs.pandium.com/reference/source-control).

If setting up an integration for the first time, Pandium is able to push an appropriate scaffolding of folders and files directly to your repository from that specific integration's detail page. Learn more about building that process and building releases [here](https://docs.pandium.com/quick-start/pandium-quick-start#build-a-release).

*Note - the files Pandium will setup for you are:*

* A package file
* A main file to print out environment
* A lib file to parse Pandium specific environment
* A default PANDIUM.yaml
* Any additional files to get a minimally running script in that specific language

A release can be also be built manually from an integration's detail page, by selecting the 'Build Release' button on the page. This will start a build for a new release, which you can track the progress of, and view logs, in the Activity table on the Source Control page.

<figure><img src="/files/fMnGZXBllSCR5FfjPWrn" alt=""><figcaption><p>The Source Control page in Admin Settings</p></figcaption></figure>

*Note: If the integration you want to build a release for has already been created, it will populate in the dropdown below the connection options, and a new release can be named and built there.*

### Source Control Webhooks

Once connected to a repository provider, you can configure webhook subscriptions to automatically trigger integration builds based on events in your repositories. After configuring your webhook settings, click **Save Webhooks** to apply your changes.

#### GitHub

| Event               | Description                                                                                                              | Filters                                                                                                                                                                                                                |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Push Events         | Triggers a build when code is pushed to a branch.                                                                        | **Commit Message Filter** — Only triggers if the commit message contains the specified string. Leave empty to trigger on all pushes.                                                                                   |
| Pull Request Events | Triggers a build when a pull request is opened or updated for any integration pointed at the pull request's head branch. | **Label Filter** — Only triggers if the pull request has the specified label. Leave empty to trigger on all pull requests.                                                                                             |
| Tag Events          | Triggers a build when a tag is created.                                                                                  | **Tag Name Filter** — Only triggers if the tag name contains the specified string. Leave empty to trigger on all tags. **Versioning Tags Only** — Only triggers on tags that follow semantic versioning (e.g. v1.2.3). |
| Release Events      | Triggers a build when a release is published.                                                                            | —                                                                                                                                                                                                                      |

#### Bitbucket

| Event               | Description                                                                                                     | Filters                                                                                                                              |
| ------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Push Events         | Triggers a build when code is pushed to a branch.                                                               | **Commit Message Filter** — Only triggers if the commit message contains the specified string. Leave empty to trigger on all pushes. |
| Pull Request Events | Triggers a build for any integration pointed at the pull request's source branch when a pull request is opened. | —                                                                                                                                    |

### Build Environment Secrets

Build Environment Secrets allow you to set key value pairs which you can reference as environment variables during the build of an integration release. A common use case is a token for an NPM registry:

<figure><img src="/files/13ZQ8kLPvW3gZuUrlWmX" alt=""><figcaption><p>The key can be whatever you want in order to access the secret value during a build.</p></figcaption></figure>

These keys can then be referenced as environment variables in the format: PAN\_SEC\_SOURCE\_CONTROL\_YOUR\_KEY\_NAME\
\
\- Note: all letters will be uppercase in the environment variable and any '-' will be converted to '\_'.

ex: My-NPM-key will be PAN\_SEC\_SOURCE\_CONTROL\_MY\_NPM\_KEY.

These build environment secrets variables can later be referenced as environment variables in your code for use during the integration build process.

Example of use in a make file:

````makefile
```makefile
GITTOKEN = ${PAN_SEC_SOURCE_CONTROL_GITHUB_TOKEN}
```
````

Example of use in a package.json:

```json
"scripts": {
    "build": "tsc --broker-token=${PAN_SEC_SOURCE_CONTROL_MY_SECRET}"
  }
```

Example of use in a .npmrc:

```ini
registry=https://your-private-registry/
always-auth=true
//your-private-registry/:_authToken=${PAN_SEC_SOURCE_CONTROL_PRIVATE_REGISTRY_TOKEN}
```

You can also make use of environment secrets directly in the PANDIUM.yaml:

```yaml
build: npm config set @pandium:registry https://registry.qa-420.pandium.com/ && npm config set //registry.qa-420.pandium.com/:_authToken $PAN_SEC_SOURCE_CONTROL_PANDIUM_TOKEN npm install --production && npm i --save-dev @types/node && npm run build
```


# Creating a GitLab App

To configure your GitLab Source Control, you must first create an OAuth2 application within your GitLab account. GitLab allows you to create an OAuth2 application at 3 different permission levels: [User](https://docs.gitlab.com/integration/oauth_provider/#create-a-user-owned-application), [Group](https://docs.gitlab.com/integration/oauth_provider/#create-a-group-owned-application), or [Instance-Wide](https://docs.gitlab.com/integration/oauth_provider/#create-an-instance-wide-application). We generally encourage you to use the Group-level OAuth2 application, but please feel free to use whichever best fits your organization.

### Creating a Group-Owned Application

To create a new application for a group:

1. Go to the desired group.
2. In the left sidebar, select **Settings** > **Applications**.
3. Enter a **Name** and **Redirect URI**.
   1. The Redirect URI will be dependent on your environment.
      1. Demo: <https://api.demo.pandium.com/v0/author/callback/oauth2>
      2. Sandbox: <https://api.sandbox.pandium.com/v0/author/callback/oauth2>
      3. Sandbox EU: <https://api.sandbox-eu.pandium.com/v0/author/callback/oauth2>
      4. Production: <https://api.pandium.io/v0/author/callback/oauth2>
      5. Production EU: <https://api.pandium-eu.io/v0/author/callback/oauth2>
4. Select OAuth 2 scopes (API Access, Read\_Repository, Write\_Repository)
5. Ensure the **Confidential** checkbox is checked.
6. Select **Save application**. GitLab provides:
   * The OAuth 2 Client ID in the **Application ID** field.
   * The OAuth 2 Client Secret, accessible by selecting **Copy** in the **Secret** field.
   * The **Renew secret** function. Use this function to generate and copy a new secret for this application. Renewing a secret prevents the existing application from functioning until the credentials are updated.
7. Notate the Application ID, Secret, and Scopes as they will be required for connecting to Pandium Source Control.


# Creating An Integration

Create internal or external integrations in Pandium by configuring connectors, repository settings, sync schedules, and source control, then building releases from a guided quick-start workflow.

The following steps will serve as a guide for creating an integration within Pandium. If this is the first time you're creating an integration or if you want a more detailed walkthrough, check out the guide for setting up an example integration [here](https://docs.pandium.com/getting-started/pandium-integration-tutorial).

### Choosing Your Integration Type

Pandium supports two types of integrations: internal and external. Internal integrations involve code execution, authentication, and run management on the Pandium platform. Most developers will be creating internal integrations. On the other hand, external integrations are primarily used for pre-existing integrations, 3rd party collaborations, or marketing purposes. Marketers or partners often create these integrations.

### Creating An Internal Integration

1. Navigate to the ‘Integrations’ resource and click ‘Create.’
2. In the pop-up widget, select ‘Internal.’
3. Configure your connectors:
   * Most integrations will need a minimum of two connectors: your SaaS and the SaaS you are integrating with. There may be instances with more than two connectors, but they are uncommon.
   * Each connector has a ‘global’ checkbox option, typically not needed. Use this when all end users will share the same authentication credentials, as in the case of an SFTP.
4. Configure your details:
   * Integration ID: A backend unique identifier for the integration. For example, if t integrating with Gorgias, it may be called “gorgias”.
   * Integration Name: The public name for the Integration. End users will use this name to gauge what type of integration it is. Using the Gorgias example, the name may be “Gorgias Helpdesk.”
5. Configure your Remote Repository Settings:
   * Repository URL: The URL where the integration code is hosted.
   * Repository Tracking Branch: The branch Pandium should use to create the integration.
   * Repository Path: The path where Pandium can find the Pandium.yaml. Leave it blank if the .YAML is in the root and is named ‘PANDIUM.yaml.’
6. Set your Sync Schedule options:
   * Customize the sync schedule options you would like to offer to your end user for this integration. You may leave the default options if you choose.
7. 'Save the Configuration
8. Provision Connectors:
   * Depending on the selected connectors, one or more of your connectors may need provisioning especially for SaaS using OAuth. Enter client id, client secret, and potentially select scopes.
   * Grab Secret Keys for development purposes, specific to the SaaS. You can read more about Environment Variable Secrets, [here](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables).
9. You're Done!

### Creating An External Integration

1\. Navigate to the ‘Integrations’ resource and click ‘Create.’

2\. In the pop-up widget, select ‘External.’

3\. Provide relevant details for the integration.

* Integration ID: This serves as a backend unique identifier for the integration. For example, a Gorgias integration might be named “gorgias”.
* Integration Name: This is the public name for the Integration, Used by end users to identify the type of integration. Using the Gorgias example, the name might be “Gorgias Helpdesk”.

4\. Fill in the associated External Integration Settings:

* External Integration Url: This is the path to install or learn more about the integration. It could be a link to the 3rd party’s Integration Marketplace if hosted externally, or a deep link to the installation URL for pre-existing integrations on your SaaS platform. If used to gather Beta end users, it might link to a Google form or info page.
* External Integration ID: This ID is sent to Pandium by your engineering team in the marketplace JWT. This field is primarily used for pre-existing integrations, ensuring Pandium accurately shows whether the integration is installed for an end user.
* External Integration Link Target: Choose how the link opens in an end-user’s browser (new tab, same page, etc).

5\. Upon saving, you’ll be prompted to fill in Marketplace Settings for this integration. You can read more about these settings here (link to Marketplace Settings).

### Connecting to Source Control

Now that the integration has been created within Pandium, confirm that Source Control is set up by navigating to the ‘Settings’ in the sidebar, then “Source Control” to connect to your repository and set up the CI/CD pipeline. For more detailed information, view [this article](https://docs.pandium.com/integration-hub/setting-up-source-control).

After connecting to Source Control, if code has been written, create a release from the “Settings” page. If not, go back to ‘Integrations”, click ‘Details’ on your new integration tile, and use the ‘Setup Integration Repo’ button to populate a quickstart for your code in your chosen language. Learn more about building releases for created integrations [here](https://docs.pandium.com/integration-hub/updating-an-integrations-release).

###

<br>


# Getting Started with Creating an Integration

Learn how create a record of your integration in Pandium, as well as configure your integration details, connectors, remote repository settings, and sync schedule options.

### Choosing Your Integration Type

Pandium supports two types of integrations: internal and external. Internal integrations involve code execution, authentication, and run management on the Pandium platform. Most developers will be creating internal integrations. On the other hand, external integrations are primarily used for pre-existing integrations, 3rd party collaborations, or marketing purposes. Marketers or partners often create these integrations.

### Creating An Internal Integration

1. Navigate to the ‘Integrations’ resource and click ‘Create.’
2. In the pop-up widget, select ‘Internal.’
3. Configure your connectors:
   * Most integrations will need a minimum of two connectors: your SaaS and the SaaS you are integrating with. There may be instances with more than two connectors, but they are uncommon.
   * Each connector has a ‘global’ checkbox option, typically not needed. Use this when all end users will share the same authentication credentials, as in the case of an SFTP.
4. Configure your details:
   * Integration ID: A backend unique identifier for the integration. For example, if t integrating with Gorgias, it may be called “gorgias”.
   * Integration Name: The public name for the Integration. End users will use this name to gauge what type of integration it is. Using the Gorgias example, the name may be “Gorgias Helpdesk.”
5. Configure your Remote Repository Settings:
   * Repository URL: The URL where the integration code is hosted.
   * Repository Tracking Branch: The branch Pandium should use to create the integration.
   * Repository Path: The path where Pandium can find the Pandium.yaml. Leave it blank if the .YAML is in the root and is named ‘PANDIUM.yaml.’
6. Set your Sync Schedule options:
   * Customize the sync schedule options you would like to offer to your end user for this integration. You may leave the default options if you choose.
7. 'Save the Configuration
8. Provision Connectors:
   * Depending on the selected connectors, one or more of your connectors may need provisioning especially for SaaS using OAuth. Enter client id, client secret, and potentially select scopes.
   * Grab Secret Keys for development purposes, specific to the SaaS. You can read more about Environment Variable Secrets, [here](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables).
9. You're Done!

### Creating An External Integration

1\. Navigate to the ‘Integrations’ resource and click ‘Create.’

2\. In the pop-up widget, select ‘External.’

3\. Provide relevant details for the integration.

* Integration ID: This serves as a backend unique identifier for the integration. For example, a Gorgias integration might be named “gorgias”.
* Integration Name: This is the public name for the Integration, Used by end users to identify the type of integration. Using the Gorgias example, the name might be “Gorgias Helpdesk”.

4\. Fill in the associated External Integration Settings:

* External Integration Url: This is the path to install or learn more about the integration. It could be a link to the 3rd party’s Integration Marketplace if hosted externally, or a deep link to the installation URL for pre-existing integrations on your SaaS platform. If used to gather Beta end users, it might link to a Google form or info page.
* External Integration ID: This ID is sent to Pandium by your engineering team in the marketplace JWT. This field is primarily used for pre-existing integrations, ensuring Pandium accurately shows whether the integration is installed for an end user.
* External Integration Link Target: Choose how the link opens in an end-user’s browser (new tab, same page, etc).

5\. Upon saving, you’ll be prompted to fill in Marketplace Settings for this integration. You can read more about these settings here (link to Marketplace Settings).

### Connecting to Source Control

Now that the integration has been created within Pandium, confirm that Source Control is set up by navigating to the ‘Settings’ in the sidebar, then “Source Control” to connect to your repository and set up the CI/CD pipeline. For more detailed information on Source Control and how to set it up, view [this article](https://docs.pandium.com/integration-hub/setting-up-source-control).

After connecting to Source Control, if code has been written, create a release from the “Settings” page. If not, go back to ‘Integrations”, click ‘Details’ on your new integration tile, and use the ‘Setup Integration Repo’ button to populate a quickstart for your code in your chosen language. Learn more about building releases for created integrations [here](https://docs.pandium.com/integration-hub/updating-an-integrations-release).


# Demo Video: Creating an Integration With Pandium

Watch an example of what the full end-to-end integration building processes looks like with Pandium.

This demo below walks through setting up an example integration between Apollo.io and HubSpot using Pandium. While the integration example may differ from your specific use case, this video is meant to provide a real-world look at the development process and the key features Pandium offers to streamline integration management.

{% embed url="<https://youtu.be/WiRFCHYSM-M?si=aiFCV3HiMCB-3HWq>" %}


# Managing Internal Integrations

Manage internal integrations in Pandium by configuring repos, default releases, sync schedules, marketplace settings, and connector secrets from a centralized integration detail page.

In order to deploy an integration through Pandium, you must first create the integration. The steps below will walk you through how to do so.

### What Are Internal Integrations?

An Internal Integration within Pandium refers to an integration where the code is running on the Pandium platform itself. In his scenario, developers will direct Pandium by providing the repository URL housing the integration code.

Pandium then creates images of the integration through [Source Control](https://docs.pandium.com/integration-hub/setting-up-source-control), overseeing the authentication process, and handling all associated activities, or [runs](https://docs.pandium.com/getting-started/key-terminology#run).

### Managing Internal Integrations

After [creating](https://docs.pandium.com/integration-hub/creating-an-integration) an internal integration, you will be directed to the primary integration detail page, where you can view and manage the integration. Here, you gain access to the the ‘Details’ section, housing relevant settings for the integration. These settings will automatically adjust to reflect any changes in the information. Additionally, the page features a table in the lower section, displaying relevant integration activities, tenants, and releases..

You will also see three tabs: Configure, Marketplace Setup, and Reprovision Connectors.

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

### Configure

To access the configuration edit page, simply click on the 'Configure' option located at the top right corner of the integration detail page. Once clicked, you will be directed to a page that closely resembles the setup page used for internal integration creation.

### Remote Repository Settings

Within the configuration edit page, you have the flexibility to modify the repository information associated with the integration. This includes the ability to change the source, branch, or yaml location, providing you with the control to tailor the integration to your specific needs.

<figure><img src="/files/WoFaXvzLeJIkMf3Kqwg7" alt="" width="563"><figcaption></figcaption></figure>

### Default Release

Under ‘Default Release’, you have the option to modify default release for the integration as well as the default release behavior for new tenants. To learn more about how each of our default release behaviors work, please view [here](https://docs.pandium.com/integration-hub/updating-an-integrations-release).

<figure><img src="/files/AJDPuhTNii04vDRW3G6k" alt=""><figcaption><p>Default Release Options</p></figcaption></figure>

### Sync Schedule

Next, let’s explore the Sync Schedule configuration. These options determine the sync intervals available for end users configuring the integration. While Pandium provides commonly used sync intervals by default, you have the flexibility to fully customize these options. To edit the cron string, please use cron format. For assistance in creating cron strings, you can use the following tool: [crontab guru](https://crontab.guru/).

Upon installation of an integration by an end user, these options will be viewed as a drop-down that your customer may choose between. You are also able to choose whether the integration should be paused by default or set to automatically begin running upon installation.

#### Disable Schedule

There may be instances where your integration doesn't require a sync schedule for your users (webhook or init sync only type of integrations). If this is the case, you should see an option available called "Disable Schedule".

<div align="left"><figure><img src="/files/IjH8n5ydpfXrMdFKESat" alt="" width="463"><figcaption></figcaption></figure></div>

When this option is toggled on, the following will occur:

* The Sync Schedule Grid configuration grid will be hidden
* The tenant data grid will reflect tenant statuses as disabled\
  ![](/files/Q0Vnvoj4h5b3pkqRhMzk)
* When pulling up a tenant, you should see the following:
  * The "Next Run" field will reflect "Disabled" and the "Schedule" field will be blank.
  * The Sync Schedule settings will reflect "Sync Schedule is disabled on this integration, tenant will not sync"
  * The create tenant flow will not include the schedule tab, and the new tenant will not run after creation

> **Friendly Tip**: The disable schedule setting is also a great option to use should your Development Team need to implement downtime efforts for integration maintenance. When the option is toggled on, all existing cron schedules for tenants will stop firing. When the toggle is turned off, any active tenants on your integration will resume their cron syncs.

### Marketplace Settings

Moving on to Marketplace settings, clicking the ‘Marketplace Settings’ tab on the integration detail page will lead you to a page where various content, copy, and media relevant to the integration's display are set. For instance, should you wish to modify the name of your integration as it appears in your marketplace, you can update this here:

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

For more information about how these settings work, please check out [this article](https://docs.pandium.com/marketplaces/marketplace-settings) to learn more.

#### Auth Dialog

By default, when a user attempts to authenticate one of the connectors, Pandium will provide default messaging that is used. However, should you or your team need to customize your user experience for a specific integration, you can do so under the "Auth Dialog" tab.

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

{% hint style="info" %}
Please note, the above instructions will override the global setting in Marketplace Settings > Content > Auth Dialog Content.
{% endhint %}

#### Success & Error Messages

Similar to the Auth Dialog Content, Pandium provides you with the option to customize the success and error messages for specific integrations as well for authentication attempts. This is a rich text field, so you can embed links and utilize placeholder keys to use values from your integration record.

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

{% hint style="info" %}
Please note, the above instructions will override the global setting in Marketplace Settings > Content > Success & Error Messages.
{% endhint %}

### Reprovision Connectors

To manage connector provisioning, navigate to the ‘Reprovision Connectors’ tab on the integration detail page. Clicking this tab will redirect you to a separate edit page. If any of the connectors you selected during your integration configuration require credential changes at the integration level, those adjustments can be made or reset from this page.

Each connector features a 'Show Secret Keys' button. Clicking this button reveals the relevant environment variable formats used for this connector, useful for development of the integration.

To learn more about these secrets, please view the [article](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables) on Environment Variable Secrets.

<figure><img src="/files/3ExsXqWN4i81FpzjOyu3" alt="" width="563"><figcaption><p>Example Provisioning Screen on an Integration</p></figcaption></figure>

There is also the option to ‘Edit Connectors’, allowing you to add or remove connectors associated with the integration. Follow the prompts to make the desired changes, establish connections with new connectors if needed, and then close the page to save the modifications and return to the integration detail page. It is important to use caution when editing connectors on an active integration.


# Creating a Tenant In the Integration Hub

Create tenants in the Pandium Integration Hub by choosing a release or channel, authenticating connectors, configuring settings and sync schedule, then saving to start or pause syncing as needed.

A tenant can be created from the main Tenants resource on the sidebar, or directly from an integration detail page by clicking 'Create Tenant' in the top right of that page.

Either way, you'll then be directed into the tenant creation flow:

<figure><img src="/files/WsqG0XTz0E8T8pqBpBek" alt=""><figcaption><p>Tenant Creation Flow</p></figcaption></figure>

Select the release that should be applied to the tenant. The 'Specific Release' option shows a dropdown where all releases that have been built for an integration show and be selected from. Learn more about building releases [here](https://docs.pandium.com/integration-hub/updating-an-integrations-release#building-releases-from-the-integration-page).

* The 'Channel' option, you can opt for either the 'Default' or 'Latest' channels.
* The 'Default' option will set the tenant so that the release version will always match the set Default release of the integration.
* The 'Latest' option guarantees that the tenant utilizes the most recently built release version for your integration, regardless of the default integration release set at the integration level.

Then, add a name for identifying the the tenant and then click 'Create' to move to the screen.

{% hint style="danger" %}
Please Note: There is a 63 character limit for tenant names.
{% endhint %}

Here, depending on the connector type that has been set up for the integration, credential fields will show for the tenant to be authenticated.

* For OAuth2.0 connectors, once the authentication flow is completed, Pandium will generate a link that you can send to your customers so that they can authenticate, and give consent to use their app. This is known as the Magic Link. Learn more [here](https://docs.pandium.com/integration-hub/managing-and-updating-tenants-in-the-admin-portal#magic-link).

Clicking 'Next' will show the configuration settings that have been defined for the integration. Learn more about how to configure customizable configurations [here.](https://github.com/pandium/external-docs/blob/master/build/anatomy-of-an-integration/pandium.yaml-spec)

* Please note, that your integration may include dynamic configurations. If this is the case, you will not be able to select these options until you finish creating the tenant and run an init sync.

On the next page, the Sync Schedule can be configured. Select a schedule type: simple or advanced.

<figure><img src="/files/emXwYHC5vghSxEDJLHKn" alt=""><figcaption><p>Sync Schedules</p></figcaption></figure>

To configure a simple schedule, simply select a sync timeframe from the options listed.

To configure an advanced schedule, you'll have to write a cron schedule expression. For assistance with this, you can use [Crontab Guru](https://crontab.guru/). Please note that the advanced scheduling capabilities are only accessible via the Integration Hub.

Lastly, decide whether the tenant should be paused by default, or immediately begin syncing according to the sync schedule.

Then, click Save Changes.

*Note: There is no limit to how many tenants you can have on a single integration.*


# Managing and Updating Tenants

Manage and update Pandium tenants by rotating connector credentials, using Magic Links, editing configs and releases, and customizing per-tenant sync schedules from the admin portal.

## Managing Tenant Credentials

Updating the credentials for a specific tenant is straightforward. Navigate to the desired tenant either through the tenant resource in the sidebar or by clicking into it from the integration or run details using the tenant name.

Tenant credentials are stored and updated in the UI via [connectors](/getting-started/key-terminology#connector) on the integration. You'll see them near the top right of the tenant detail page under 'Connector Status'. Disconnect the connector that you want to update the credentials for, then reconnect and enter the new credentials that you want to use. If it is an OAuth connector, a URL we call the Magic Link will be generated that will take you through the rest of the approval process. This Magic Link can also be used to send to your customers for a headless authentication process without having to use Pandium at all.

### Magic Link

The Magic Link is the authentication URL generated and stored on the connector associated with the integration. This URL is unique for each tenant and remains the same even if the specific tenant is disconnected and reconnected. When connecting as a Pandium user, this link takes you through the necessary external authentication process. Upon completion, you can return to Pandium and proceed.

This link is also useful for your customers, who can authenticate without ever using Pandium or needing an account. You can send this link to your customer and they will go through the authentication process on their end.

Once either you or your customer has successfully authenticated, Pandium will consider them connected and you will see this reflected on the Connector Status within Pandium by a green check.

<figure><img src="/files/gBkfiH2AtcbXaWE78n1h" alt=""><figcaption><p>Example of the tenant detail and connection status</p></figcaption></figure>

### Connector Info

For tenants that utilize connectors that may have extra information you may want to surface via the UI - certain webhook integrations as a prime example - there is an 'Info' button that will display under the connector name on the tenant detail page.

This will be greyed-out if there no such information set in the connector yaml information. This information is set by Pandium on the connector - if there is certain information you think would be helpful, contact your Pandium TAM.

<figure><img src="/files/Ol5XPLgD6wDXXxAW4wEb" alt=""><figcaption><p>Example of the info button on a connector</p></figcaption></figure>

## Renaming a Tenant

For whatever reason you need to update the name of your tenant, navigate to the tenant detail page, then click on 'Settings' in the top right. You'll be brought to the tenant configs page where you can update this information.

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

Once you have made the necessary changes, simply press SAVE CHANGES down below and you should see the name updated accordingly.<br>

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

## Updating Tenant Configurations and Releases

Your integrations are able to have a multitude of configuration options set in the [PANDIUM.yaml](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec) file that are then able to display in the admin dashboard when setting up or modifying tenant configurations. On top of this, when you have multiple tenants on a single integration, you may want to have certain tenants running on different versions, or releases, of an integration.

To change a configuration option of a tenant, or a release version, navigate to the tenant detail page, then click on 'Settings' in the top right. You'll be brought to the tenant configs page where you can set this information.

If you have optional settings or flows that can be set, you'll do that here before saving the new settings.

If you want to change or set a tenant release version, you are able to do so under the 'Version' heading.

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

Here, if you select the Specific Release option, you'll be able to choose from the dropdown and select from the releases that you've built for your integration. Learn more about that process and how it works with our Source Control [here](/reference/source-control).

By choosing the Channel option, you can select either the Default or Latest channels. The Default option will set the tenant so that the release version will always match the set Default release of the integration. The Latest option will ensure that the tenant uses the most recently built release version for your integration, regardless of the Default integration release set at the integration level.

## Updating a Tenant Sync Schedule

To change how often a tenant syncs, you can individually set the sync schedule for each tenant. Navigate to the tenant detail page, and click 'Sync Schedule' in the top right. This action will display the sync schedule settings specific to that tenant.

To configure a simple schedule, simply select a sync timeframe from the options listed. To configure an advanced schedule, you will have to write a cron schedule expression. For assistance with this, you can use [Crontab Guru](https://crontab.guru/). Please note that the advanced schedule can only be altered from the Admin Dash.

Lastly, decide whether you would like to have your tenant paused, or whether you would like it to begin running right away, and then hit Save to apply the changes!

<figure><img src="/files/emXwYHC5vghSxEDJLHKn" alt=""><figcaption><p>Example of sync schedule settings</p></figcaption></figure>


# Managing and Updating Releases

Manage Pandium integration releases by building new versions via Source Control, setting default release behavior, and applying multi-YAML releases to tenants from the integration detail page.

Pandium simplifies the process of releasing a new version of an integration script and apply that code to existing tenants.

Releases are built through Pandium [Source Control](https://docs.pandium.com/integration-hub/setting-up-source-control). To view or manage these releases, start by navigating to the Integration Detail page.

### Building Releases From the Integration Page

![Example integration showing the 'Build Release' button](/files/2SitwHlQckmbcVNLIxJt)

Once an integration is created within Pandium, users can automatically populate integration scaffolding directly in the repository, including folder structure and language-specific files. This feature is beneficial for initiating development on an integration or when a change in repository information necessitates new scaffolding.

To start the process, simply click the ‘Setup Integration Repo’ button. This will then take you back to the Source Control page, where you can monitor the build starting in the Activity section.

If using this feature, Pandium will set up the following for you:

* A package file
* A main file to print out environment
* A lib file to parse Pandium specific environment
* A default PANDIUM.yaml
* Any additional files to get a minimally running script in that specific language

Alternatively, a manual build for a release can be started two different ways: clicking the ‘Build Release’ button on the main integration detail page, or via the Source Control tab in the ‘Settings’ resource.

In either case, Source Control is triggered to search for the defined path and repository information set in your integration configuration, subsequently constructing a new release based on those changes.

## Setting a Default Release

![](/files/Bg2uyMMxOmJ0NBPJfQFn)

Within the integration configuration page, where repository settings are defined, locate the "Default Release" section. This default release becomes the version automatically provisioned to your customers upon installing the integration from your marketplace. It's crucial to have a default release selected for customers to install the integration. To select a release from the dropdown, you have to have built one or more release versions via[ source control](https://docs.pandium.com/platform-guides/engineering-guides/source-control).

There are three separate options for the default release behavior:

1. Static Default: Installed tenants maintain the currently set default integration release, remaining unchanged unless manually reset in the administrator platform.
2. Dynamic Default: Installed tenants are created using the current default integration release. If the integration default release changes, installed tenants will synchronize to match the new release on the next sync.
3. Latest: Tenants will always use the most recently-built release available for this integration.

{% hint style="info" %}
**Please Note:** When upgrading your release versions for your integrations, if there are any new configurations with default values, newly created tenants will reflect these values; all previously created tenants for your integration will not reflect these new default values. In order for these new changes to apply, you need to navigate to each respective tenant on your integration, pull up their tenant connection settings, and hit SAVE.
{% endhint %}

### Multi-yaml Support for Integrations

In the dynamic landscape of integrations, Pandium provides flexibility by enabling the creation and management of releases on an integration with multiple YAML files.

To use a different YAML file than the current setting, simply edit the repository path with the new YAML file path, then click ‘Build Release’ on the Integration detail page. Source Control will build a new release, ready to be applied to any tenant.

<br>


# Managing External Integrations

Manage external integrations in Pandium by configuring marketing-only tiles, target URLs, and marketplace theming to showcase third-party or existing apps without running code on Pandium.

### What are External Integrations?

External Integrations in Pandium differ from [Internal Integrations](https://docs.pandium.com/integration-hub/setting-up-your-first-integration-tile) because they’re designed for creating integrations for pre-existing and third-party integrations.

This is most relevant when embedding a marketplace and wanting marketing-focused tiles without any installation capabilities, wanting Pandium to show what pre-existing non-Pandium integrations are installed, or highlighting 3rd-party integrations.

External integrations are also useful to allow your partners to create app tiles that don’t require advanced configuration setup. This is also particularly useful for the [Public Gallery](https://docs.pandium.com/marketplaces/public-gallery), where tiles can be made purely with marketing content if desired.

### Managing External Integration

After [creating](https://docs.pandium.com/integration-hub/creating-an-integration) an external integration, you will land on the main integration detail page where you can view and manage the integration.

External integrations are simple to update. Clicking ‘Configure’ on the top right of the integration detail page will bring you to the external integration edit page, where you are able to change the target URL, name, and how the app appears when clicked.

<figure><img src="/files/g5vaRQDQiHY3OzUR44VE" alt=""><figcaption><p>External Integration Edit Page</p></figcaption></figure>

You also have control over various theming options, which can be accessed by clicking 'Marketplace Settings' on the top right of the integration detail page.

This page shows various options for editing the content, copy, and media that displays on your user-facing integration tile. Learn more about the various settings [here](https://docs.pandium.com/marketplaces/marketplace-settings).


# Managing Tenant Connection Settings

Configure Pandium tenant connection settings by selecting releases and managing static or dynamic configs, with dynamic options refreshed via init syncs from the Hub or In-App Marketplace.

The connections setting page can be used to configure an instance of an integration (i.e. a tenant) to run according to its end user’s needs. This is important when an integration has multiple tenants whose end users may have different needs.

The connection settings page can be accessed from the Integration Hub by going to the tenant detail page and clicking the settings button.

An integration end user can also access the connections settings page from the In-App Marketplace by going to 'My Apps', locating the card for an installed app, clicking the three-dot button to open the app’s dropdown, and clicking 'Connection Settings' from the dropdown.

### Release Configurations

When accessed from the Integration Hub, the first configuration option on the Connection Settings page is always the integration release, i.e. which version of the integration the tenant will run. To learn more about Pandium’s release management features, review [this article](https://docs.pandium.com/integration-hub/updating-an-integrations-release).

Other configuration options on the Connections Settings page will vary based on the integration. These integration-specific options can either be static or dynamic.

#### Static Configurations

A configuration is called static when its options are the same for every tenant. Each tenant’s end user may make a different selection, but the same options are presented to every tenant.

Common static configurations allow certain flows to be enabled/disabled and basic options to be selected.

For example, a Gorgias <> Klaviyo integration could include the following options:

* Enable a flow to sync Gorgias customers to Klaviyo profiles
* Enable a flow to sync Gorgias Events to Klaviyo events
* A multi select menu for the end user to choose which standard types of Gorgias events should be synced to Klaviyo.

#### Dynamic Configurations

A configuration is called dynamic when its options vary from tenant to tenant. For example, each Klaviyo account has its own set of lists that can be used to organize profiles.

A Gorgias <> Klaviyo integration could have the following dynamic configuration:

* A multi select menu that provides Klaviyo lists for that tenant. Since each tenant will be connected to a different Klaviyo account, the options in this menu will be different for each tenant.
* The integration end user would be able to select the Klaviyo list to which new profiles should be added when they are synced to Klaviyo.

The options to populate a dynamic config must be fetched from the API during an init sync

When a tenant is first created through the Integration Hub, it will initially display “placeholder” instead of any account specific options. After an init sync has been run for the tenant, the account specific options should appear for all dynamic configs on the connection settings page.<br>

<figure><img src="/files/0gd24uaMd5xdI8xrbQwD" alt=""><figcaption><p>Klaviyo List selector with a placeholder before init sync is run</p></figcaption></figure>

Compared to the following:

<figure><img src="/files/UbJ87aLVskpBsHtTSSqB" alt=""><figcaption><p>Klaviyo List selector with dynamic options after the init sync</p></figcaption></figure>

There are multiple ways in which an init sync can be triggered:

* In-App Marketplace - Initial Install:
  * When the end user installs an integration that uses dynamic configs via the In-App Marketplace, Pandium will automatically run an init sync before the end user can fill out any configuration data. This means In-App Marketplace end users should not see the “placeholder” option for a dynamic config.
* In-App Marketplace - Refresh Button
  * An end user can trigger an init sync by clicking the 'refresh configs' button when configuring the app via the In-App Marketplace.
* Integration Hub - Init Sync
  * An init sync can also be triggered manually int the Integration Hub when viewing a tenant, clicking Sync Now, and selecting Init sync in the top right.

An init sync can also be triggered manually int the Integration Hub when viewing a tenant, and clicking the Sync Now, and selecting Init sync in the top right.

<figure><img src="/files/JTLXJYg3pHRwdDUdoWfk" alt=""><figcaption><p>Tenant Init Sync Option on Integration Hub</p></figcaption></figure>

The static and dynamic options in the connections setting page are determined by the integration’s PANDIUM.yaml. To learn more about how the PANDIUM.yaml is used for this, review this article.


# Creating Users

Add new Pandium users by going to Settings → Users, entering their name, email, organization, and letting them activate access via an emailed login and password update link.

To add a new user to Pandium, navigate to “Settings” in the navigation sidebar, then “Users” in the top bar within the Settings resource.

Navigate to the Users tab, and click the “Add New User” button near the top right to access the user creation page.<br>

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

Add in the new user's details: Name, email address, and organization. For all non-partner users, the organization will be the name of your company. You can also directly add users to your partner organizations by selecting them from the dropdown.

<figure><img src="/files/5Rpbc0S3X3RE33Wf28mH" alt=""><figcaption></figcaption></figure>

After creation, the newly added user will receive an email from Pandium prompting them to login to Pandium and update their password.

Congratulations! You have successfully added a user.

<br>


# Managing Users

Manage Pandium Integration Hub users by granting full admin access, inviting new team members, and relying on JWT-based identification for end users in embedded marketplaces.

### How to Manage Integration Hub Users

Currently, there is only one level of users in Pandium’s Integration Hub, with the exception of partners. All users added to Pandium will be able to create and edit integrations, create and delete tenants, and change marketplace settings.

To add new users and view, delete, or reset passwords for any other users, navigate to 'Settings' in the navigation sidebar of the Integration Hub and then “Users.”

The only user who can not be deleted is the user designated as the primary administrator for your organization.

When adding a new user, the new user will receive an email from Pandium, prompting them to create a new password.

### In-App Marketplace Users

Users using the In-App Marketplace do not need to be manually created. Rather, when the marketplace is created and embedded within a SaaS tool, a JSON Web Token (JWT) is created and configured as part of the embedding.

This JWT will contain a unique identifier, specific to the SaaS, that will tell Pandium what integration end-user is utilizing the marketplace.

This is how Pandium knows what integrations to show as installed and what integrations to list as available. Directions for configuring the JWT for specific groups or classes of users can be found [here](https://docs.pandium.com/marketplaces/customizing-the-jwt).

<br>


# Administrator Settings

Configure Pandium administrator settings by managing company details, users, notifications, partner and integration forms, source control, and API keys from a centralized Settings area.

In the Integration Hub, click on the ‘Settings’ link in the sidebar to access the following tabs:

*Note: Partner users will only see the Company, Users, and Source Control tabs. Pandium Lite accounts won't see tabs related to partners (e.g., Integration Form and Partner Form tabs).*

Below is an explanation of what each tab in this area entails.

### Company

Under the Company tab, you can set the primary administrator for your account, and you also have the option to set logos that will display to your partners in their Integration Hub experience.

If using marketplace features, you can also set the base URL, which is the URL of the iframe where you've installed the In-App Marketplace.

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

### Users

This tab shows all users associated with your Pandium Integration Hub, including their role and organization. If you have a partner organization, partner users are also displayed and managed [here](https://docs.pandium.com/integration-hub/creating-and-managing-users).

![](/files/g5vaRQDQiHY3OzUR44VE)

### Notifications & Webhooks

In the Notifications tab, you can toggle various events to trigger email notifications. The primary contact under the 'Company' tab will always be notified of those events, regardless of the emails entered here.

Should you wish to receive a webhook notification for these same events, if you enter a valid URL, the "Send Webhook" toggle will activate for each of the respective options. See [here](https://app.gitbook.com/o/-MfEyg17bPwE_6Fs6Y-q/s/-MfJn-9R_dn6dvcGNcdk/~/edit/~/changes/423/integration-hub/notifications-and-webhooks) for more information on notifications.<br>

<figure><img src="/files/4nmCcJWGiKkFSO6oBnMs" alt="" width="563"><figcaption></figcaption></figure>

### Integration Form

Here, you have the option to set custom questions for partners to answer during their integration creation process before submitting it for approval. Partners will see this at the end of an integration creation process.

This form is shown for every integration that is created for a given partner, so specific information can be collected per integration. You can view these answers in the Configure area of the Integration Detail page.

<figure><img src="/files/pjy32ckugkT9t8EG7SkW" alt="" width="563"><figcaption></figcaption></figure>

### Partner Form

This is similar to the Integration Form, but partners will only need to fill this form out once. The Partner Form you create here is what invited partners see upon initial login to Pandium but before accessing the Integration Hub.

Learn more about the Partner Form [here](https://docs.pandium.com/partners/partner-integration-form).

<figure><img src="/files/VAA00MfMMr7gKUWFY8Nt" alt="" width="563"><figcaption></figcaption></figure>

### Source Control

[Source Control](https://docs.pandium.com/getting-started/key-terminology#source-control-integration) is an integration between Pandium and your chosen code repository.

Source Control is automatically deployed to your account, but in order to use it, you will need to login to your repository from this page. Learn more about setting up Source Control [here](https://docs.pandium.com/integration-hub/setting-up-source-control).

<figure><img src="/files/DZ5C2SeldomqD5xb1P0e" alt="" width="563"><figcaption></figcaption></figure>

### API Access

Here, API keys can be generated by Pandium for certain tasks, including kicking off runs from outside Pandium. Learn more about the Pandium API [here](https://docs.pandium.com/reference/pandium-api).

<figure><img src="/files/NzrmxrlUx8FKp4DyNR7d" alt="" width="563"><figcaption></figcaption></figure>

###


# Notifications & Webhooks

The Notifications & Webhooks tab in Administrator Settings allows you to configure email and webhook notifications for key platform events. The primary contact designated in the Company tab receives all event notifications automatically, regardless of additional email addresses configured.

## Configuration

### Webhook Configuration

A single webhook URL is shared across all notification types. Enter a valid URL in the **Webhook URL** field to enable webhook delivery. Once a valid URL is provided, a **Send Webhook** toggle becomes active for each event, allowing you to selectively enable webhook notifications per event type.

{% hint style="info" %}

* The URL must be a valid HTTP/HTTPS endpoint
* Webhooks are sent as `POST` requests with `Content-Type: application/json`
* Your endpoint should return a 2xx status code to indicate successful receipt
  {% endhint %}

### Email Subscribers

Each event type supports a comma-separated list of email addresses. These subscribers receive email notifications in addition to the primary contact.

### Event Types

| Event                       | Key                           | Descripti                                               |
| --------------------------- | ----------------------------- | ------------------------------------------------------- |
| Integration Created         | integration\_created          | Fired when a new integration is created on the platform |
| Tenant Created              | tenant\_created               | Fired when a new tenant is created for an integration   |
| Tenant Archived             | tenant\_archived              | Fired when a tenant is archived                         |
| Partner Integration Updated | partner\_integration\_updated | Fired when a partner modifies an existing integration   |
| Run Failed                  | run\_failed                   | Fired when an integration run fails                     |

## Webhook Payload Shapes

All webhook payloads share a set of common fields, with additional fields specific to each event type.

### Common Fields

Every webhook payload includes:

```json
{
  "event_type": "<event_key>",
  "environment": "<namespace>",
  "account_id": 12345,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z"
}
```

| Field         | Type    | Description                                                          |
| ------------- | ------- | -------------------------------------------------------------------- |
| event\_type   | string  | The event key (e.g. `"new_integration_created"`, `"tenant_created"`) |
| environment   | string  | The namespace/environment where the event occurred                   |
| account\_id   | integer | The ID of the organization that owns the resource                    |
| account\_name | string  | The name of the organization                                         |
| timestamp     | string  | ISO 8601 / RFC 3339 timestamp in UTC                                 |

### Integration Created

Fired when a new integration is created.

```json
{
  "event_type": "new_integration_created",
  "environment": "prod-acme",
  "account_id": 100,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z",
  "integration_id": 42,
  "integration_name": "Shopify Integration"
}
```

| Field             | Type    | Description                             |
| ----------------- | ------- | --------------------------------------- |
| integration\_id   | integer | The ID of the newly created integration |
| integration\_name | string  | The display name of the integration     |

### Tenant Created

Fired when a new tenant is created for an integration.

```json
{
  "event_type": "new_tenant_created",
  "environment": "prod-acme",
  "account_id": 100,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z",
  "tenant_id": 789,
  "tenant_name": "Widget Co",
  "integration_id": 42,
  "integration_name": "Shopify Integration"
}
```

| Field             | Type    | Description                                     |
| ----------------- | ------- | ----------------------------------------------- |
| tenant\_id        | integer | The ID of the newly created tenant              |
| tenant\_name      | string  | The name of the tenant                          |
| integration\_id   | integer | The ID of the integration the tenant belongs to |
| integration\_name | string  | The display name of the integration             |

### Tenant Archived

Fired when a tenant is archived.

```json
{
  "event_type": "tenant_archived",
  "environment": "prod-acme",
  "account_id": 100,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z",
  "tenant_id": 789,
  "tenant_name": "Widget Co",
  "integration_id": 42,
  "integration_name": "Shopify Integration"
}
```

| Field             | Type    | Description                                      |
| ----------------- | ------- | ------------------------------------------------ |
| tenant\_id        | integer | The ID of the archived tenant                    |
| tenant\_name      | string  | The name of the tenant                           |
| integration\_id   | integer | The ID of the integration the tenant belonged to |
| integration\_name | string  | The display name of the integration              |

### Partner Integration Updated

Fired when a partner modifies an existing integration.

```json
{
  "event_type": "partner_integration_updated",
  "environment": "prod-acme",
  "account_id": 100,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z",
  "integration_id": 42,
  "integration_name": "Shopify Integration",
  "partner_id": 55,
  "partner_name": "Partner Inc"
}
```

| Field             | Type    | Description                                             |
| ----------------- | ------- | ------------------------------------------------------- |
| integration\_id   | integer | The ID of the updated integration                       |
| integration\_name | string  | The display name of the integration                     |
| partner\_id       | integer | The ID of the partner organization that made the change |
| partner\_name     | string  | The name of the partner organization                    |

### Run Failed

Fired when an integration run fails. Includes direct links to the run in both the admin dashboard and the API.

```json
{
  "event_type": "run_failed",
  "environment": "prod-acme",
  "account_id": 100,
  "account_name": "Acme Corp",
  "timestamp": "2026-05-04T12:00:00Z",
  "run_id": 9876,
  "run_status": "failed",
  "admin_link": "https://admin.prod-acme.pandium.com/runs/9876/show",
  "api_link": "https://api.prod-acme.pandium.com/v2/runs/9876",
  "api_status_link": "https://api.prod-acme.pandium.com/v2/runs/9876/status",
  "api_triggers_link": "https://api.prod-acme.pandium.com/v2/runs/9876/triggers",
  "tenant_id": 789,
  "tenant_name": "Widget Co",
  "integration_id": 42,
  "integration_name": "Shopify Integration"
}
```

| Field               | Type    | Description                                   |
| ------------------- | ------- | --------------------------------------------- |
| run\_id             | integer | The ID of the failed run                      |
| run\_status         | string  | The status of the run (e.g. `"failed"`)       |
| admin\_link         | string  | Direct link to the run in the admin dashboard |
| api\_link           | string  | API endpoint for the run resource             |
| api\_status\_link   | string  | API endpoint for the run's status             |
| api\_triggers\_link | string  | API endpoint for the run's triggers           |
| tenant\_id          | integer | The ID of the tenant the run belongs to       |
| tenant\_name        | string  | The name of the tenant                        |
| integration\_id     | integer | The ID of the integration                     |
| integration\_name   | string  | The display name of the integration           |

## Delivery Details

* **Method**: `POST`
* **Content-Type**: `application/json`
* **Retries**: Webhooks are sent once with no automatic retry on failure
* **Timeout**: Standard HTTP timeout applies
* **Authentication**: No authentication headers are sent with webhook requests... if you need to verify the source, validate by IP or use the webhook URL as a shared secret (e.g. include a token as a query parameter)&#x20;


# Site Metrics

Within the Integration Hub, you'll find a sidebar resource called Site Metrics. This section provides detailed metrics for site traffic, whether you're using our in-app marketplace or your own external one.

*Note: Site Metrics are not included in the Pandium Lite offering.*

### Metrics Powered By Pandium Platform

Using the Pandium Integration Hub to host your integrations, in addition to utilizing the In-App Marketplace, will allow for more granular metrics to be available for you integrations, including unique visitors vs page views, device and browser information, filtering by specific pages and users, etc.

You will also have access to specific event tracking for each integration depending on your configuration. Each event is broken down by which user performed the action.

<br>

![](/files/ygS6i86OojMBlaJjYWJ0)

Each of these events can be clicked to get further information on them:

* View - Shows when a user views an integration
* Delete - Shows when a tenant has been uninstalled
* Sync - Shows when a user runs a sync
* Connect - Shows when a user clicks connect
* Disconnect - Shows when a user disconnects the connector
* Schedule - Shows when a scheduling change occurred
* Configure - Shows when a configuration change occurred
* Install - Shows when an integration has been successfully installed

![Events will only show in the submenu if there are relevant entries](/files/EcsUdRbWPyeVQh8k7FNL)

### Metrics With An External Marketplace

If you exclusively utilize the Pandium marketplace to show integrations that are not hosted on Pandium, i.e. legacy integrations or external integrations, you can still track several events related to your integration tiles in this page, although information gathered will be more limited than integrations hosted on Pandium’s infrastructure.


# Bulk Actions

Learn about upcoming Pandium Bulk Actions features for efficiently managing admin tasks across multiple integrations, tenants, or runs from a single place.

As a Pandium admin user, there may be times when you need to update releases or pause a few tenants during integration maitenance. Rather than perform these tasks one-by-one, there are a few resources you can use to update items in bulk.

### Tenants

Whether you are reviewing your list of tenants from the tenants page or within a specific integration, you should see the following three options available to you if you select multiple tenants at once.

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

* **Pause Sync** - This action pauses the sync schedule for your tenant(s)
* **Resume Sync** - This action unpauses the sync schedule for your tenant(s)
* **Upgrade Release** - If you select this option, you will be presented with a pop-up window that will allow you to specify a specific release or release channel:

  <figure><img src="/files/8qGkxbRz3EyZRCPrMLCa" alt=""><figcaption></figcaption></figure>

You can surface these options by selecting multiple tenants at once or by selecting the checkbox located at the top header column.

*Please Note: When selecting multiple tenants from the Tenants page, you can only select "Upgrade Release" if the tenants selected all belong to the same integration.*&#x78;

{% hint style="info" %}
Please Note: If you choose to upgrade the release for any of your existing tenants using this method, if there are any new configurations with default values, in order for these new changes to apply, you need to navigate to each respective tenant, pull up their tenant connection settings, and hit SAVE.
{% endhint %}

<figure><img src="/files/7LI9275RUqcyZ177kqRv" alt=""><figcaption><p>From the Tenant Page</p></figcaption></figure>

<figure><img src="/files/2DpEkTA8vi9s95ZRzp65" alt=""><figcaption><p>From the Tenant Tab via an Integration</p></figcaption></figure>

* **Download Tenants -** This option will return your selected results in a .csv format and contain the following information:
  * id
  * name
  * integration\_id
  * integration\_name
  * integration\_release\_tag
  * integration\_release\_name
  * created\_date
  * schedule\_status
  * last\_run\_date
  * last\_run\_status<br>

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

### Runs

Whether you are reviewing your list of runs from the Runs page or within a specific integration, you should see the following options available to you if you select multiple runs at once.

* **Download Runs** - This option will return your selected results in a .csv format and contain the following information:
  * run
  * integration\_id
  * integration\_name
  * tenant\_id
  * tenant\_name
  * mode
  * trigger
  * started\_date,
  * completed\_date,
  * status
* **Download Logs** - This option will allow you to download a standard .txt copy of the run details. However, rather than having to do this run-by-run, you can now do this in batches of up to 50 runs at a time

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

### Releases

If you navigate to a specific integration and look at the "Releases" tab, you should see the ability to select multiple releases at once. When you do this, you should see the ability to delete your releases (one at a time or in bulk).

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


# Reruns

Rerun Pandium syncs by selecting any past run, choosing original, current, or custom configs and stdout, and optionally saving rerun state to influence future tenant runs.

As a Pandium user, there may be instances where I need to rerun a sync for one of my tenants. It's possible that my initial run didn't capture all the necessary data that I was hoping to see synced over. This could be because details or values in the target systems may not have been properly set, or maybe my team has simply updated our tenant configuration settings or switched to a new release version. Whatever the case may be, you have the ability to trigger a rerun for a previously synced job.

To trigger a Rerun, perform the following steps:<br>

1. Pull up the tenant in question
   1. You can do this by pulling up the integration or by going to Manage > Tenants
2. Navigate to the run details page for the job you wish to rerun
   1. You can also locate the desired run by navigating to Manage > Runs and filter out your search results using the enhanced sorting and filtering resources.
3. Once you have pulled up the run details page, look for the **RERUN** option in the top right of your screen and select it.<br>

   <figure><img src="/files/JU9vqrQ5afqiQkgXdWRj" alt="" width="563"><figcaption></figcaption></figure>
4. You should now be presented with a popup window that says "Are you sure you want to rerun this run?". You should see three options listed below:
   1. **Rerun with original configuration** - When selecting this option, you will be using the integration release, STDOUT, and configs that the original received.
   2. **Rerun with current configuration of tenant** - When selecting this option, you will be using the integration release, STDOUT, and configs that are currently set on the tenant.
   3. **Rerun with custom configuration** - When selecting this option, you will be presented with the opportunity to customize the integration release, STDOUT, and configs for this rerun, with default values populated from the original run.<br>

      <figure><img src="/files/YLsDmLxCkVIPcDxBxWAQ" alt=""><figcaption></figcaption></figure>
5. Select one of the options above and press NEXT.
   1. If you selected **Rerun with original configuration** or **Rerun with current configuration of tenant,** you should be presented with a new option that says "Do you want to save the [STDOUT](https://docs.pandium.com/getting-started/anatomy-of-an-integration/environment-variables/stdout) and context from this rerun to be used in future runs?" By default, No will be selected.<br>

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

      1. <mark style="color:red;">**Please Note:**</mark> If you select NO, since the STDOUT is not being saved, should will not see this reflected in "last\_run" when using the [/v2/tenants](https://docs.pandium.com/reference/pandium-api#tenants) endpoint.
   2. If you select **Rerun with custom configuration**, then you will be taken to a new screen that will allow you to modify the Release, STDOUT, and Configuration details. Once you have modified your details and selected RERUN, then you will be asked if you wish to save the STDOUT and context from this rerun to be used in future runs.

      1. **Release** - Do you want to use a different release version?
      2. **STDOUT** - Modify what will be passed into the environment variables PAN\_CTX\_LAST\_SUCCESSFUL\_RUN\_STD\_OUT and PAN\_CTX\_LAST\_RUN\_STD\_OUT.
      3. **Configuration** - Are there any tenant settings that you missed last time?<br>

      <figure><img src="/files/TisMf7L18SJX5SR1RCGV" alt=""><figcaption><p>The great thing about this option is that you do not have to directly change your tenant settings.</p></figcaption></figure>
6. Once you have selected one of the following rerun options, you will be brought back to the tenant details page and there should be a new run created with a trigger that says rerun.\ <br>

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

   1. <mark style="color:red;">**Please Note:**</mark> If you pull up the details for this new run, you will notice the rerun option is greyed out even after the sync finishes. We currently do not support the ability to rerun a rerun. If you wish to trigger another rerun for the original run ID, then you will need to start from steps 1 and 2 respectively.\ <br>


# Sorting, Filtering, & Log Searches

Quickly find integrations, tenants, runs, partners, and users in Pandium using rich sorting, filtering, and Boolean log search tools across the Integration Hub and In-App Marketplace.

As a Pandium user, there may be instances where you need to quickly parse out specific integration-related details without having to manually sift page by page. You may be managing a large number of tenant runs and need to find ones with a specific status, or maybe you're looking for tenants that use a particular release version to troubleshoot an issue. This could be because you're onboarding a new team member who needs to understand your current setup, or maybe you're auditing, or simply performing integrations maintenance.<br>

In other circumstances, you might need to quickly find specific data that has been processed within one of your integrations, or even better, empower your end users to find this data themselves.\
\
Whatever the case may be, you can utilize sorting and filtering capabilities to help navigate and organize your integration tech stacks more effectively, as well as use log searches to identify discrete data.

## **Pages That Utilize Sorting and Filtering**

1. Integrations
   1. Integration Details
      1. Runs
      2. Tenants
      3. Releases
2. Tenants
   1. Tenant Details
3. Runs
4. Partners
5. Users

### Integrations

When viewing your list of integrations, you can filter out your integrations by type ([External](/integration-hub/managing-external-integrations) vs [Internal](/integration-hub/setting-up-your-first-integration-tile)) and also if the integration was developed by a Partner.

For more information regarding working with Partners, please see [here](https://docs.pandium.com/partners/inviting-partners).

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

#### Integration Details

Once you have located a specific integration that you would like to review, if you select "details", you will see the following sorting and filtering options available to use within the following tabs:

1. **Runs** - This will display a list of all runs associated with integration-specific tenants.

   1. Tenant Name
   2. Mode - Was the run an Init, Normal, or Webhook Sync?
   3. Trigger - Depending on how your integration is set up, end users can initiate syncs on a Pandium tenant in multiple ways. If that is the case, you have the ability to parse out specific runs based on said triggers.
      1. Cron
      2. Webhook
      3. Manual
      4. API
      5. Rerun
   4. Status - You can parse out specific run jobs based on the following statuses:
      1. Succeeded
      2. Failed (Integration Issue)
      3. Failed (Timeout)
      4. Failed (Platform Issue) - Issue with Pandium
      5. Failed (Refresh) - Issue with the refresh process
      6. In Progress
      7. Initializing
      8. Automatic Retry
   5. Start Date & End Date - You can filter out your runs between a specific time period. When filling this out, feel free to use the calendar or enter in using the format *mm/dd/yyyy hh:mm*<br>

   <figure><img src="/files/7uwMUQskL689dPO5FmCo" alt=""><figcaption></figcaption></figure>
2. **Tenants** - This will display a list of all tenants specifically associated with your integration
   1. Customer or Tenant Name
   2. Tenant Status - Is the tenant active or not active?
   3. Integration Release - Sometimes you may have different tenants on varying releases, so this filter allows you to single out tenants tied to a specific release.
   4. Schedule Status - Is the sync scheduled paused, active, or disabled?

      Paused = ![](/files/AqxpQICcnTn5VEWg08Zb)

      Active = ![](/files/9Fy9WUj2A4Xdn2R4AQbl)

      Disabled = ![](/files/KBv149eUdaRzu5cmlONw)
   5. Last Run Status - This will use the same list of statuses used for Runs
   6. Created After & Created Before - You can filter out your list of tenants created during a specific time period. When filling this out, feel free to use the calendar or enter in using the format *mm/dd/yyyy hh:mm*<br>

      <figure><img src="/files/H8QaH2HW5onAGW9eEjcg" alt=""><figcaption></figcaption></figure>
3. **Releases**

   1. Release or Tag - This will allow you to filter by the specific name of your release
   2. Created After & Created Before - You can filter out all releases that are associated with your integration created during a specific time period. When filling this out, feel free to use the calendar or enter in using the format *mm/dd/yyyy hh:mm*

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

### Tenants

Similar to sorting and filtering tenants at an integration-specific level, you should see the following options when looking at all of your tenants across all integrations:

1. Customer or Tenant Name
2. Integration - Which integration are these tenants associated with?
3. Schedule Status
4. Paused
5. Last Run Status
6. Created After & Created Before

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

#### Tenant Details

Once your have identified which tenant (or list of tenants) you want to inspect, if you select anywhere within the row, it will bring you to the specific tenant details. In here, you should see the ability to sort all runs associated with this tenant using the following options:

1. Mode
2. Trigger
3. Status
4. Start Date & End Date

<figure><img src="/files/4eIy6G3SQeI40zKH035d" alt=""><figcaption></figcaption></figure>

### Runs

canSimilar to sorting and filtering runs from within a specific integration, you have the ability to parse out runs across all of your integrations using the following options:

1. Integration - Which integration does this run belong to?
2. Tenant (Name)
3. Mode
4. Trigger
5. Status
6. Start Date & End Date

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

### Partners

Should you and your team decide to collaborate with a third-party team to help with integration builds, Pandium makes it easier to sort and filter through any list of existing partners. If you navigate to Manage > Partners, you should see the following:<br>

1. From the main partner page, you should see the option to search by Partner Organization Name and Partner Status (Approved vs Pending)<br>

   <figure><img src="/files/s4ju7SbKSAh520hVGUjH" alt=""><figcaption></figcaption></figure>
2. Once you have selected a specific partner organization, this will bring up specific partner-related details. You should see sorting and filtering options similar to what what we discussed above in the Pandium admin dashboard based on the following sections:
   1. Tenants
   2. Runs
   3. Integrations
   4. Users

### Users

Lastly, you should be able to utilize sorting capabilities when reviewing your own internal users. Simply navigate to Setup > Settings > Users. From here, you should be able to sort by:

1. Name or Email of your user
2. Role - Is this user an admin or partner?
3. Organization - Is this user a member of your own internal org or a partner org?
4. Enabled - Is the user active or inactive?

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

## **Pages That Utilize Log Searches**

1. Integration Hub
   1. Integrations
      1. Integration Details
         1. Search across logs for all tenants
   2. Tenants
      1. Tenant Details
         1. Search across all logs within the tenant
2. Integration Marketplace
   1. Tenants
      1. Sync Logs
         1. Search across all logs within the tenant

### Integration Hub

#### Integrations

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

On the integration detail page, above the runs, tenants, and release data grids, you will find a button called 'Search Logs'. Upon clicking this button, you will be navigated to a new log search page, automatically filtered for the integration you are viewing.

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

The log search supports basic Boolean Expressions, including OR and AND logic (as shown above). The query will show all results across all logs for all tenants within this integration. You can navigate to a specific log from here and will be redirected to the log show page with the relevant information highlighted.

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

#### Tenants

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

On the tenant detail page, above the runs data grids, you will find a button called 'Search Logs'. Upon clicking this button, you will be navigated to a new log search page, automatically filtered for the tenant you are viewing.

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

The log search supports basic Boolean Expressions, including OR and AND logic. The query will show all results across all logs for this tenant. You can navigate to a specific log from here and will be redirected to the log show page, with the relevant information highlighted.

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

### Integration Marketplace

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

On your integration marketplace, within your customers' grid of installed tenants, they can navigate to view run logs for a specific tenant.

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

On the right of the page, above the run logs list, there is a 'Search Logs' button. Upon hitting this button, you will be navigated to the search page, where your customers can search for any query across their run logs for this tenant. The log search supports basic Boolean Expressions, including OR and AND logic. The query will show all results across all logs for this tenant. Upon clicking a log, it will expand and have the relevant information highlighted.

<figure><img src="/files/0Q7rkRwncSDzwrTeaQ8K" alt=""><figcaption></figcaption></figure>


# Integration Onboarding Experiences Overview

Embed Pandium’s white-labeled integration marketplace, setup UI, or auth pop-up to streamline onboarding, handle OAuth/API keys, and drive integration adoption with minimal engineering.

Alongside our integration platform, we offer flexible, optional solutions to help you get your integrations adopted, while reducing the effort required to build and maintain onboarding flows.

**We offer three customizable, embeddable options for integration onboarding UIs and first-time user experiences:**

* [**In-App Marketplace**](/marketplaces/integration-onboarding-experiences-overview/embedding-the-marketplace) – Embed a fully branded, white-labeled integration marketplace directly into your app.
* [**Embedded Integration Install**](/marketplaces/integration-onboarding-experiences-overview/embedding-the-integration-install-only) – Embed a secure, fully managed integration setup UI into an existing marketplace.
* [**Embedded OAuth**](/marketplaces/integration-onboarding-experiences-overview/embedding-auth-only-connections) – Embed an authentication pop-upin an existing marketplace. Allow users to securely connect to external systems without any manual configuration.

For all three options, Pandium handles OAuth flows and API key exchanges behind the scenes. Each can be embedded into your app or marketplace using an iframe.

**Watch the 2-minute video below to see these options in action:**

{% embed url="<https://drive.google.com/file/d/1jIpANGh2z_FnF05V0EA3qdQ7-YrBmXoR/view?usp=sharing>" %}

<br>


# Embedding the In-App Marketplace

Embed Pandium’s In-App Marketplace via iframe and JWT-based SSO to securely show user-specific integrations, installations, and configs inside your app without an extra login.

### How Does the In-App Marketplace Work?

The Pandium In-App Marketplace product offering is designed to be displayed in an iframe within a web app that sits behind your company's login. In order to maintain the security of your user's data, we suggest that you stand up a backend service that redirects to the Pandium In-App Marketplace with your user's information encoded in a JSON Web Token (JWT).

This single sign-on (SSO) mechanism that allows your site to pass information about your users to Pandium and tells Pandium that the user has been authenticated. Pandium uses that information to securely display your users’ specific integration configurations without an extra login.

*Note: The Marketplace and related features are not included in the Pandium Lite offering.*

### How to Embed the Marketplace

#### Prerequisites

Before your site can be enabled for SSO via JWT with Pandium, you will need to reach out to the Pandium support and exchange the below:

* A shared secret supplied by Pandium. This is used to sign the JWT, and helps Pandium ensure the requests come from you and you alone.
* If embedding a Pandium Marketplace in your application, we'll also need the domain of the application that will serve as the iframe's parent. Pandium needs this for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) purposes.
  * *Note: If you are using a Sandbox or PoC environment, we will not need the domain.*

#### Getting Started

Your application will need to direct your users to a url that looks similar to the below:

`https://imp.pandium.io/<account>?tenant=<signed_jwt_token>` if using a production Pandium account.

`https://imp.sandbox.pandium.com/<account>?tenant=<signed_jwt_token>` for Sandbox Pandium accounts.

`https://imp.demo.pandium.com/<account>?tenant=<signed_jwt_token>` for Pandium trial accounts (PoCs).

Pandium customers typically embedded this URL as an iframe in their applications or pop-out to a new tab or window.

The account name is a version of your company name, and will either be provided to you, or, if you have already received your login information from Pandium for your In-App Marketplace, you can find it in the url, e.g. `https://imp.sandbox.pandium.com/yourcompanyname?tenant=.`

With this, Pandium will take the token and display a list of all integrations that the user can install. You can also deep link to user's installed integrations, or a specific integration in the marketplace.

#### Framework of a Sample Backend Service in Python

```python
import time
import uuid

import falcon
from jwt import encode


class PandiumSSOJWTEndpoint:
    def __init__(self, config):
        self.config = config

    def on_get(self, req: falcon.Request):

        payload = {
            'iat': int(time.time()),
            'jti': str(uuid.uuid4()), # Required.
            'external_id': '',  # Not Required. Add this if the unique id you use for your user is not the same as email address
            'meta': '',  # Not Required. Free form object to associate with your user in Pandium
            'sub': '',  # Required. Email address of your user. Pandium uses this to link our tenant to your user's account in your system
        }

        jwt = encode(payload, self.config['PANDIUM_SHARED_SECRET'], algorithm='HS256')
        sso_url = f"https://{self.config['PANDIUM_SUB_DOMAIN']}.go.pandium.com/?tenant={jwt}"

        raise falcon.HTTPTemporaryRedirect(sso_url)


app = falcon.API()
app.add_route('/pandium-sso', PandiumSSOJWTEndpoint({'PANDIUM_SHARED_SECRET': '', 'PANDIUM_SUB_DOMAIN': ''}))
```

#### Creating the Signed JSON Web token

You will need to build a JWT containing the users’ data in a backend service.

JWTs are made of 3 parts separated by a period (`.`). Each piece is [base64Url](https://tools.ietf.org/html/rfc4648#section-5) encoded which then gets assembled to look like below:

`<base64url-encoded header>.<base64url-encoded payload>.<baseurl-encoded signature>`

#### Header:

Pandium currently supports the following header:

```javascript
{
    "alg": "HS256",
    "typ": "JWT"
}
```

The base64url-encoded version of the above is below:

```javascript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

```javascript

{
 "iat": 1621521641,
 "jti": "1cfa7dbf-8110-4237-ad22-410608791b7d",
 "ti": {
   "udn": "Pandium Test",
   "ufn": "Important Person",
   "uem": "test@pandium.com",
   "ili": [
     "new-id",
     "something-different"
   ],
   "aid": "",
   "adn": "",
   "xti": {
     "extraProp": "extra value",
     "extraList": [
       "bla",
       "listVal"
     ]
   }
 },
 "sub": "test-pandium-com"
}


```

#### Signature:

The JWT signature is produced by concatenating the Base64url encoded header with the Base64url encoded claims, and then signing using the shared secret using HMAC with SHA-256.

```javascript
HMAC-SHA256(base64url-encoded(header) + "." + base64url-encoded(payload)), <shared secret>)
```

#### A Complete Example.

```javascript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

```


# Embedding the Integration Install Only

Embed Pandium’s Integration Install Only UI via iframe and JWT SSO to let users authenticate, configure, and schedule integrations inside your marketplace while keeping native styling.

## How does Integration Install Only work?

Pandium offers the option to embed a secure, fully managed integration setup UI into your marketplace or partner marketplaces. This first-time-user-experience (FTUX) flow presents itself via a page where users can log-in and authenticate to different systems, configure their integration settings, and sync schedules—all within a UI that you can customize.

This page is displayed in an iframe within your web app, sitting behind your company’s login.

\
This can be useful for creating a dedicated area within your site where you control which connectors or integrations users can access. It provides a simple way to integrate with Pandium's advanced integration management platform while retaining your custom marketplace styling. This is a good option for those who wish to utilize more of Pandium's native integration options, with minimal additional developer support, but maintain a fully native and custom Marketplace experience.

## How to Embed the Integration Install Flow

### Prerequisites

Before your site can be enabled for SSO via JWT with Pandium, you will need to reach out to the Pandium support and exchange the below:

* A shared secret supplied by Pandium. This is used to sign the JWT, and helps Pandium ensure the requests come from you and you alone.
* If embedding a Pandium Marketplace in your application, we'll also need the domain of the application that will serve as the iframe's parent. Pandium needs this for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) purposes.
  * *Note: If you are using a Sandbox or PoC environment, we will not need the domain.*

### Getting Started

Each integration that you are surfacing to your users will need to direct to a specific URL where your users can authenticate. The URLs will look like the below:

`https://imp.pandium.com/<account>/tenants/create/<integration_id>?tenant=<jwt_token>` if using a production Pandium account.

`https://imp.sandbox.pandium.com/<account>/tenants/create/<integration_id>?tenant=<jwt_token>` if using a sandbox Pandium account.

In these URLs, the organization name is your unique company name, which can be found in the URL while logged into the Integration Hub URL, e.g. `https://imp.sandbox.pandium.com/yourcompanyname?tenant=`, and the specific Connector name being used, which can be found in integration the object via our [API](https://docs.pandium.com/reference/pandium-api).

Additionally, within the JWT, each connection will need to have fields defined in the '`xti`' field under the '`ti`’ property in your [JWT](https://docs.pandium.com/marketplaces/customizing-the-jwt), as seen below in the example with a connector named 'gwt' and integration named 'gwt2hs':

```
"ti": {
    "xti": {
        connector_name: "gwt",
        integration_name: "gwt2hs",
    }
}
```

**For Auth Dialog to function, the JWT will require a token parameter on your side for&#x20;*****your organization's*****&#x20;connector, so that when users connect, they are able to authenticate into your system.**

### Creating the Signed JSON Web token

You will need to build a JWT containing the users’ data in a backend service.

JWTs are made of 3 parts separated by a period (`.`). Each piece is [base64Url](https://tools.ietf.org/html/rfc4648#section-5) encoded which then gets assembled to look like below:

`<base64url-encoded header>.<base64url-encoded payload>.<baseurl-encoded signature>`

#### Header

Pandium currently supports the following header:

```
{
    "alg": "HS256",
    "typ": "JWT"
}
```

The base64url-encoded version of the above is below:

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

```
{
 "iat": 1621521641,
 "jti": "1cfa7dbf-8110-4237-ad22-410608791b7d",
 "ti": {
   "udn": "Pandium Test",
   "ufn": "Important Person",
   "uem": "test@pandium.com",
   "ili": [
     "new-id",
     "something-different"
   ],
   "aid": "",
   "adn": "",
   "xti": {
     "extraProp": "extra value",
     "extraList": [
       "val1",
       "listVal"
       ]
     "connector_name": "gwt",
     "integration_name": "gwt2hs",
     "token": "your token"
   }
 },
 "sub": "test-pandium-com"
}
```

#### Signature

```
HMAC-SHA256(base64url-encoded(header) + "." + base64url-encoded(payload)), <shared secret>)
```

The JWT signature is produced by concatenating the Base64url encoded header with the Base64url encoded claims, and then signing using the shared secret using HMAC with SHA-256.

#### A Complete Example

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```


# Embedding Auth-Only Connections

Embed Pandium Auth-Only Connections via iframe and JWT SSO to let users authenticate external systems and auto-install integrations with no visible configuration steps.

### How Do Auth-Only Connections Work?

Pandium offers an Auth-Only Connection option that lets you embed a custom, configure-less (for the user) authentication page directly into your system. This page allows users to log in directly to different systems, and automates the integration installation process without them ever seeing Pandium. This page is displayed in an iframe within your web app, sitting behind your company’s login.\
\
This can be useful for creating a dedicated area within your site where you control which connectors or integrations users can access. It provides a simple way for users to authenticate into a system and have integrations installed without any extra steps.

To maintain the security of your user's data, you must stand up a backend service that redirects to the Auth-Only Connection page with user information encoded in a JSON Web Token (JWT).

#### Difference Between In-App Marketplace and Auth-Only Connections

While similar to Pandium's In-App Marketplace, there are key differences between the two features:

The **In-App Marketplace** provides a more holistic marketplace experience, where you can show various integration options and app detail data to both potential and existing users, and provide them directed workflows to set configurations, schedules, and install apps manually.

With **Auth-Only Connections**, you can simplify this process through background connect, i.e. users are automatically authenticated to your system when connecting apps, and immediately directed to authenticate into the external system with no configuration requirements needed by the user.

This is accomplished by first, defining what systems you even want to display to users on this page through the JWT, and second, setting specific configuration fields in the JWT.

Learn more about the process for setting up Auth-Only Connections below.

### Embedding Auth-Only Connection Page

Before your site can be enabled for SSO via JWT with Pandium, you will need to reach out to the Pandium support and exchange the below:

* A shared secret supplied by Pandium. This is used to sign the JWT, and helps Pandium ensure the requests come from you and you alone.
* If embedding a Pandium Marketplace in your application, we'll also need the domain of the application that will serve as the iframe's parent. Pandium needs this for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) purposes.
  * *Note: If you are using a Sandbox or PoC environment, we will not need the domain.*

#### Configuring the Auth-Only Experience

Each system that you are surfacing to your users will need to direct to a specific URL where your users can authenticate. The URLs will look like the below:

If using a Sandbox account, the URL will be: `https://emp.sandbox.pandium.com/{orgname}/connector/auth?tenant={jwt}`

If using a production account, the URL will be:

`https://emp.pandium.io/{orgname}/connector/auth?tenant={jwt}`

In these URLs, the organization name is your unique company name, which can be found in the URL while logged into the Integration Hub URL, e.g. `https://imp.sandbox.pandium.com/yourcompanyname?tenant=`, and the specific Connector name being used, which can be found in integration the object via our [API](https://docs.pandium.com/reference/pandium-api).

Additionally, within the JWT, each connection will need to have fields defined in the '`xti`' field under the '`ti`’ property in your [JWT](https://docs.pandium.com/marketplaces/customizing-the-jwt), as seen below in the example with a connector named 'gwt' and integration named 'gwt2hs':

```
"ti": {
    "xti": {
        connector_name: "gwt",
        integration_name: "gwt2hs",
    }
}
```

**For Auth Dialog to function, the JWT will require a token parameter on your side for&#x20;*****your organization's*****&#x20;connector, so that when users connect, they are able to authenticate into your system.**

#### Setting up the JWT and Backend Service

To setup the JWT and backend services needed to enable your marketplace or auth-only features, take a look at the below example:

#### Framework of a Sample Backend Service in Python

```python
import time
import uuid

import falcon
from jwt import encode


class PandiumSSOJWTEndpoint:
    def __init__(self, config):
        self.config = config

    def on_get(self, req: falcon.Request):

        payload = {
            'iat': int(time.time()),
            'jti': str(uuid.uuid4()), # Required.
            'external_id': '',  # Not Required. Add this if the unique id you use for your user is not the same as email address
            'meta': '',  # Not Required. Free form object to associate with your user in Pandium
            'sub': '',  # Required. Email address of your user. Pandium uses this to link our tenant to your user's account in your system
        }

        jwt = encode(payload, self.config['PANDIUM_SHARED_SECRET'], algorithm='HS256')
        sso_url = f"https://emp.pandium.com/{self.config['ORG_NAME']}/connector/auth?tenant={jwt}"

        raise falcon.HTTPTemporaryRedirect(sso_url)


app = falcon.API()
app.add_route('/pandium-sso', PandiumSSOJWTEndpoint({'PANDIUM_SHARED_SECRET': '', 'ORG_NAME': ''}))
```

#### Creating the Signed JSON Web token

You will need to build a JWT containing the users’ data in a backend service.

JWTs are made of 3 parts separated by a period (`.`). Each piece is [base64Url](https://tools.ietf.org/html/rfc4648#section-5) encoded which then gets assembled to look like below:

`<base64url-encoded header>.<base64url-encoded payload>.<baseurl-encoded signature>`

#### Header:

Pandium currently supports the following header:

```javascript
{
    "alg": "HS256",
    "typ": "JWT"
}
```

The base64url-encoded version of the above is below:

```javascript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```

```javascript

{
 "iat": 1621521641,
 "jti": "1cfa7dbf-8110-4237-ad22-410608791b7d",
 "ti": {
   "udn": "Pandium Test",
   "ufn": "Important Person",
   "uem": "test@pandium.com",
   "ili": [
     "new-id",
     "something-different"
   ],
   "aid": "",
   "adn": "",
   "xti": {
     "extraProp": "extra value",
     "extraList": [
       "val1",
       "listVal"
       ]
     "connector_name": "gwt",
     "integration_name": "gwt2hs",
     "token": "your token"
   }
 },
 "sub": "test-pandium-com"
}


```

#### Signature:

The JWT signature is produced by concatenating the Base64url encoded header with the Base64url encoded claims, and then signing using the shared secret using HMAC with SHA-256.

```javascript
HMAC-SHA256(base64url-encoded(header) + "." + base64url-encoded(payload)), <shared secret>)
```

#### A Complete Example.

```javascript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

```

### iFrame Post Message

Once the user successfully connects via the auth dialog we will send a postMessage from the iframe window which your application can then use to track the user's state. The event will have tenant data as well as a webhook url you can use to register webhooks for the tenant in your system. This can be used if you don't have a backend endpoint we can register webhooks with during connect.

Example MessageEvent:

```json
"data": {
    "message": "pandium-success",
    "webhook_url": "https://api.pandium.io/v1/webhooks/{tenant specific webhook hash}",
    "tenant": {
        "archived": false,
        "name": "test-tenant",
        "namespace": "staging-gwt",
        "configs": {},
        "created_date": "2024-03-11T18:45:41.532727",
        "paused": false,
        "user_schedule": "*/30 * * * *",
        "schedule": "8-59/30 * * * *",
        "source": "admin",
        "integration": {
            "id": 1,
            "name": "test-integration"
        },
        "integration_release": null,
        "integration_release_channel": "Default",
        "id": 11,
        "modified_date": "2024-03-11T18:45:41.532729",
        "connected_users": {
            "pandium": {
                "id": "112fd91d-e197-4910-8da3-8c7174934d2f",
                "email": "test@pandium.com",
                "username": "test-pandium-com",
                "attributes": {
                    "jwt": {
                        "display_name": "Pandium Test",
                        "full_name": "Important Person",
                        "external_integrations": "some-int",
                        "auditable_uid": "1800",
                        "auditable_display_name": "Auditable Person",
                        "extra_tenant_info": "{\"extraProp\":\"extra value\",\"extraList\":[\"one\",\"listVal\"],\"connector_name\":\"test-connector\",\"integration_name\":\"test-integration\",\"token\":\"some-token\"}",
                        "namespace": "staging-gwt",
                        "keycloak_uri": "authz.nc.pandium.io"
                    }
                },
                "first_name": null,
                "last_name": null,
                "email_verified": true,
                "enabled": true,
                "realm_id": "staging-gwt",
                "created_timestamp": 1709151776419
            }
        },
        "status": {
            "tenant_id": 11,
            "last_run": {},
            "current_run": {},
            "last_successful_run": {},
            "dynamic_configs": {},
            "auth": {
                "connected": true,
                "TEST_AUTH_STATUS": "Connected",
                "TEST_LAST_CHANGE": "2024-03-15T14:04:02.96Z",
                "GWT_AUTH_STATUS": "Connected",
                "GWT_LAST_CHANGE": "2024-03-15T14:04:02.05Z"
            }
        }
    }
}
```


# Customizing the JWT

Use Pandium Auth-Only Connections to embed a secure iframe and JWT-based SSO flow that silently authenticates users to external systems and auto-installs integrations without extra setup.

At a minimum, the JWT (JSON Web Token) passed from your application to the Pandium In-App Marketplace must offer a unique identity for the user accessing the marketplace. Nevertheless, the token’s functionalities go beyond simply logging a user into Pandium.

By customizing the JWT, you can configure various options, like displaying specific integrations to certain users, defining user groups, and executing other actions tailored to your requirements.

*Note: The In-App Marketplace and related features are not included in the Pandium Lite offering.*

## Customizing Marketplace Views

When configuring your marketplace, you might need distinct views based on user and app groupings.

This customization can be seamlessly achieved by manipulating a JWT. By specifically identifying users and apps through email and ID within the xti (extra tenant information) field in the JWT, and following predefined rules set by Pandium to support these rules, you can unlock several possibilities.

Below are a few relevant use cases for changing the marketplace view:

**Displaying Unique Apps to a Single User**

If you wish to present certain integrations to a user selectively, you can refer to the app ID in the JWT. Specify a list, for example, using a field like ‘hidden\_integrations,’ and those apps will not be visible to specific user(s).

**Showing Different Marketplaces By a User Group**

Similarly, you can define a group, such as ‘user\_group’ or ‘user\_tier’ in the xti field. List the apps you want to display for that particular group.

#### Managing Integration Installs Based on User Groups

Once again, within the same xti field, you can define a user group and introduce parameters like ‘allowed\_installs.’ This feature can limit the number of apps a user is permitted to install. For additional use cases, please reach out to your Technical Account Manager so that we can collaborate on supporting these functionalities.

*Note: The Marketplace and related features are not included in the Pandium Lite offering.*

## Linking Legacy Integrations in the In-App Marketplace

If you have existing apps linked to existing "legacy" integrations and wish to showcase them in the embedded marketplace, enabling customers to link out to installed instances, follow these steps:

\
1\. Create an External Integration - find instructions [here](/integration-hub/setting-up-your-first-integration-tile).

2\. In the JWT, under the external integration ID field represented by ili, insert the value passed into the JWT. If the customer has the integration installed, it will be visible in the marketplace; if not, it won't show, and the app will appear normally.

3\. In the external integration URL field on an external integration, paste the URL where the integration lives in your system.

The JWT payload is where your application encodes custom information or claims about your user.

Refer to table below for a list and descriptions of the claims your JWT should contain:

### JWT Payload:

The JWT payload is where your application will encode the custom pieces of information, or claims, about your user.

The table below provides a list and descriptions of the claims your JWT should contain.

| Claims | type   | Required | Description                                                                                                                                                                                                          |
| :----: | ------ | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   iat  | string |     x    | Time the token was generated. The value must be the number of seconds since UNIX epoch. If this is more then 1 minute Pandium will reject the token.                                                                 |
|   jti  | string |     x    | A unique id for the token that is used to protect against replay attacks by making sure the token is used only once.                                                                                                 |
|   sub  | string |     x    | Unique ID of the primary user in your system. This may be an email, site id, or other identifying value. Pandium uses this to uniquely identify the user. If the user does not exist in Pandium, it will be created. |
|   ti   | object |          | The Tenant Info Object. This object is used to pass information about the end-customer user that you need to tell Pandium about.                                                                                     |

The table below provides a list and descriptions of the properties of the ti object mentioned above.

| Property | Type          | required | Description                                                                                                                                                                                                                                                                                |
| -------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| tn       | string        |          | tn (tenant name) is used to set the name of the tenant. If not included will default to a random uuid. The first and last character needs to be lowercase alphanumeric, can include the following special chars "- \_ . ". invalid names will be sanitized by removing invalid characters. |
| udn      | string        |          | The user's display name                                                                                                                                                                                                                                                                    |
| ufn      | string        |          | The user's full name                                                                                                                                                                                                                                                                       |
| uem      | string        |          | The user's email                                                                                                                                                                                                                                                                           |
| ili      | array(string) |          | An array of installed external integrations, whose ids match the ids of the integration tiles in the Marketplace, that are currently active, and would like to be shown as such in the Marketplace.                                                                                        |
| aid      | string        |          | Auditable user id (if different from sub)                                                                                                                                                                                                                                                  |
| adn      | string        |          | Auditable users display name (if different from sub)                                                                                                                                                                                                                                       |
| xti      | object        |          | A place to store extra props that may be need to power your company's marketplace experience                                                                                                                                                                                               |

### Sample:

```javascript

{
 "iat": 1621521641,
 "jti": "1cfa7dbf-8110-4237-ad22-410608791b7d",
 "ti": {
   "tn": "Tenant Name"
   "udn": "Pandium Test",
   "ufn": "Important Person",
   "uem": "test@pandium.com",
   "ili": [
     "new-id",
     "something-different"
   ],
   "aid": "",
   "adn": "",
   "xti": {
     "extraProp": "extra value",
     "extraList": [
       "bla",
       "listVal"
     ]
   }
 },
 "sub": "test-pandium-com"
}


```

### Signature:

The JWT signature is produced by concatenating the Base64url encoded header with the Base64url encoded claims, and then signing using the shared secret using HMAC with SHA-256.

```javascript
HMAC-SHA256(base64url-encoded(header) + "." + base64url-encoded(payload)), <shared secret>)
```

### A complete example.

```javascript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

```


# Marketplace Settings

Customize Pandium marketplaces by configuring themes, fonts, colors, components, sorting, and content so your In-App Marketplace or Public Gallery matches your brand and UX.

Whether using the [In-App Marketplace](https://docs.pandium.com/marketplaces/embedding-the-marketplace) or the [Public Gallery](https://docs.pandium.com/marketplaces/public-gallery), there are many marketplace options that can be customized as needed. To access these settings, simply navigate to the ‘Marketplace’ resource on the sidebar to get started.

## Marketplace Theme <a href="#marketplace_theme" id="marketplace_theme"></a>

The Theme tab contains general theming setting for the marketplace.

![](/files/vAgEKO8BGJLJXuEYabj9)

#### Primary Color <a href="#primary_color" id="primary_color"></a>

You are able to select a primary color using either the graphic interface, a hex code, or a decimal code. The color selected here will be the primary color scheme on your [In-App Marketplace](https://docs.pandium.com/marketplaces/embedding-the-marketplace), appearing on your buttons and search bar.

#### Secondary Color

You are able to select a secondary color using either the graphic interface, a hex code, or a decimal code. The color selected here will be the secondary color scheme on your [In-App Marketplace](https://docs.pandium.com/marketplaces/embedding-the-marketplace), appearing on your links.

### Text Styling & Formatting <a href="#text_styling" id="text_styling"></a>

Explore a variety of preloaded fonts in Pandium to set your primary font effortlessly. If you have a specific font preference not currently supported, please contact your Technical Account Manager for assistance.

#### Title Font Size <a href="#title_font_size" id="title_font_size"></a>

Effortlessly adjust the font size of all titles on your In-App Marketplace using the Title Font Size dropdown menu.

#### Subtitle Font Size <a href="#subtitle_font_size" id="subtitle_font_size"></a>

Use the Subtitle Font Size dropdown menu to set the font size for all subtitles on your In-App Marketplace.

#### Date Format <a href="#date_format" id="date_format"></a>

Personalize your integration marketplace with a selection of date formats. Simply click the dropdown menu to discover our date format combinations and codes, following UTC #35.

### Configure Components <a href="#configurecomponents" id="configurecomponents"></a>

Here, you'll be able to toggle and set various optional components that can be used in the marketplace.

<figure><img src="/files/EvdrcrMCWsxzzm3lf0Ne" alt="" width="563"><figcaption></figcaption></figure>

#### Marketplace App Sort Order <a href="#marketplace_app_sort_order" id="marketplace_app_sort_order"></a>

Here, you can choose how you want your Marketplace apps to be ordered. Choose between Alphabetical sorting, arranging apps based on Integration ID in alphabetical order, or Order Index sorting, which numerically orders them from lowest to highest.t. The "Order Index" value is set on the integration’s Marketplace Settings within the Integration Hub. If integrations have the same value or a null value, they are sub-sorted alphabetically within Order Index sorting.

#### Display Search Bar

Here, you can choose whether or not you wish to provide a search bar on your In-App Marketplace.

#### Display Categories

Here you can choose whether to leverage the categories feature within your In-App Marketplace by choosing to display or hide them.

#### Display Carousel

Highlight a Featured section above your other apps in the marketplace using the Display Carousel feature. Customize the featured apps in the carousel and the name displayed in this section through the Content tab.

To enable an app to appear here, use the toggle in an integration’s Marketplace Setup; further details can be found [here](https://docs.pandium.com/integration-hub/setting-up-your-first-integration-tile#step-4-marketplace-settings).

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

#### Separate Marketplace and Apps

Here you're able to choose whether you would prefer your customers' installed apps to appear above the In-App Marketplace or if you would prefer that they are broken out into two separate pages.

#### Separate Marketplace Button

By toggling this option, your marketplace and installed applications will live on two discreet pages that your end user can navigate between.

#### Search Bar for Installed Apps

If you have the Separate Marketplace toggled on, then this option will provide an additional search bar on your customer's "My Apps" page for them to search between installed applications.

#### Include Pause in Sync Schedule Dropdown

Here you can choose to display the pause button icon below your list of sync schedule options or whether you want it displayed as an option in your sync schedule dropdown.

#### Search Icon Color <a href="#search_icon_color" id="search_icon_color"></a>

You are able to select a color for your Marketplace Search Icon using either the graphic interface, a hex code, or a decimal code.

![](https://desk.zoho.com/DocsDisplay?zgId=707992423\&mode=inline\&blockId=jkqkje68734bbd9f74e8c8493996211536817)

#### Tool Tips Icon Color <a href="#tool_tips_icon_color" id="tool_tips_icon_color"></a>

You are able to select a color for your Tool Tips Icons using either the graphic interface, a hex code, or a decimal code.

#### Card Border Radius

This allows you to modify the roundness of the tiles shown in the marketplace.

**Carousel Width**

This controls the overall width of the featured app carousel on the main page of the marketplace.

#### Card Shadow Options

Below the Border Radius options, you'll find three CSS input fields to configure the shadow settings of your tiles. Leaving these fields blank will apply default shadow options. Customize these settings by pasting your CSS, and observe the changes in both the In-App Marketplace and the Gallery.

#### Enable Background Connect

If you’ve collaborated with the Pandium team to enable SSO connection for the apps within your In-App Marketplace, you can use this option to turn this feature on.

### Marketplace Content

In the Marketplace Settings tab, you can customize a range of text components on your In-App Marketplace. This includes text on the main marketplace page and the tile detail pages. Align these adjustments with your company's unique branding.

![Example Content page of Marketplace Settings](/files/0trS1jjUqyklqwSxxCfS)

*Note: The In-App Marketplace and related features are not included in the Pandium Lite offering.*

#### Auth Dialog Content

By default, when a user attempts to authenticate one of the connectors, Pandium will provide default messaging that is used. However, should you or your team need to modify this to customize your user experience, as part of the Marketplace Content options, you can adjust the auth dialog.

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

{% hint style="info" %}
Please note, the above instructions are for adjusting the Global Auth Dialog Content for your Marketplace Settings. If you wish to customize this on an integration-level, please see navigate to the specific integration record marketplace settings adjust your content there. For more information, please reference the following [help article](https://docs.pandium.com/integration-hub/setting-up-your-first-integration-tile#marketplace-settings).
{% endhint %}

#### Success & Error Messages

Similar to the Auth Dialog Content, Pandium provides you with the option to customize the success and error messages shown after authentication attempts. This is a rich text field, so you can embed links and utilize placeholder keys to use values from your integration record.

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

<figure><img src="/files/0xtABUCaOnGO5CocDVBN" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Please note, the above instructions are for adjusting the Global Success & Error Messages for your Marketplace Settings. If you wish to customize this on an integration-level, please see navigate to the specific integration record marketplace settings adjust your content there. For more information, please reference the following [help article](https://docs.pandium.com/integration-hub/setting-up-your-first-integration-tile#marketplace-settings).
{% endhint %}


# Flags, Tags, and Categories

Configure flags, tags, and categories in Pandium to highlight key integrations, boost search, and organize marketplace tiles without writing any code.

## Overview <a href="#overview" id="overview"></a>

You can configure flags, tags, and categories in your marketplace without any coding. This allows you to highlight new or exceptional integrations, making it easier for your customers to find those most useful to them.

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

### Flags <a href="#flags" id="flags"></a>

A flag is a bold UI feature added to your integration tile, highlighting a specific aspect of the integration or multiple integrations. Examples include New, Partner Built, Certified, or Premier. Each integration can have only one flag, but you can customize the color for any flag type.<br>

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

### Tags <a href="#tags" id="tags"></a>

Tags function as backend identifiers and do not visually appear on your marketplace. Instead, they enable your team to attach specific words or phrases to integrations, enhancing the search function. You can assign as many tags to an integration as needed.

### Categories <a href="#categories" id="categories"></a>

Categories help in organizing the integrations and applications in your marketplace. By default, these categories appear on the left-hand sidebar. An integration can be assigned to multiple categories or subcategories, and categories can be separated into sections using section breaks.

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

## How to Create Tags, Flags, and Categories <a href="#how_to_create_tags_flags_and_categories" id="how_to_create_tags_flags_and_categories"></a>

1. Sign in to your Pandium Admin Dashboard and click on the Marketplace resource in the sidebar.
2. Click on the item you are attempting to create (Categories, Flags, or Tags).
3. Click the “Create” button on the appropriate dashboard to create a new category, flag, or category.
4. Flags and Tags will automatically become available, but Categories must be toggled ‘on’ to appear in your Marketplace.

   * In the popup creation window, you will be able to select the creation of a category, subcategory, or section break.

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

   * These elements can be organized by dragging and dropping them in order from the left side of each row using the dotted icon. This ordering will reflect on both the In-App Marketplace and the Public Gallery site.
5. Next, navigate to your Integrations resource and then click into the integration tile you would like to add categories, subcategories, tags, or a flag to
6. Within the integration view page, click on “Marketplace Setup” to view the integration’s marketplace configuration page.
7. The third, fourth, and fifth fields on this page will correlate to categories, tags, and flag. You are able to multi-select categories and subcategories for the integration, multi-select tags for the integration, and select a flag for the integration.
8. Don’t forget to save at the bottom of the page!

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


# Custom CSS

Customize your Pandium marketplace beyond built-in settings by uploading, editing, and saving custom CSS (or scripts) in the Custom CSS tab, with draft previews before publishing.

In the instance that your team would like to override Pandium's built-in options and embed your own CSS styles, or maybe you just want to upload your own embedded scripts, you can accomplish all this from the Custom CSS Tab.

<figure><img src="/files/BAy1TNDP3uO8IIBGHNTb" alt="" width="563"><figcaption></figcaption></figure>

When working with the CSS editor, all of your activity in real-time will be marked as a draft, which you should see icon for so:<br>

<figure><img src="/files/CjJximVOGrab2AWZ8lxt" alt="" width="563"><figcaption></figcaption></figure>

Using the Editor, you should see the following four options:

* **Upload** - If you want to upload a CSS file, simply select the "Upload" button, and this will display in the editor as a “draft”.
* **Download** - If there is existing content in the editor, this will allow you to download this information as a .css file
* **Reset** - When editing your draft, if you make a mistake or simply wish to start over, this option allows you to reset the contents of the editor to the most recently saved state.
* **Save** - Once any of your drafts are completed and the content looks good to you, feel free to hit SAVE, and you should see your changes immediately reflected in your marketplace. You'll know these changes have been saved because the "Draft" flag icon will be removed as well.

As you customize your marketplace, please refer to our sample file for available class names, which you can find in the sample file.

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


# Marketplace Events

In addition to the multiple customization options and ways of implementing the Pandium Marketplace, we also support emitted events from the iframe that you can use to trigger additional custom actions. or navigate outside of the iframe.&#x20;

The Pandium Marketplace iframe communicates with your parent application using the [browser's window.postMessage() API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). When a user performs an action inside the iframe (installing, saving, navigating, etc.), Pandium sends a structured message to the parent window. Your application listens for these events using [window.addEventListener('message', ...)](https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event) and can respond accordingly. For example, closing a modal, navigating to another page, or showing a confirmation.\
\
Each message contains a `message` field with the event name, and depending on the event, a `tenant` object with the tenant and integration IDs.

#### Example Listener

```
window.addEventListener('message', (event) => {
    // Always verify the origin in production
    // if (event.origin !== 'https://your-pandium-domain.com') return

    const { message, tenant } = event.data

    switch (message) {
        case 'pandium-install-cancel':
            console.log('User cancelled install for tenant:', tenant?.id)
            closeModal()
            break
        case 'pandium-save-sync':
            console.log('User saved and synced tenant:', tenant?.name)
            showSuccessNotification()
            break
        case 'pandium-content-height-change':
            // Resize the iframe to match content
            const iframe = document.getElementById('pandium-iframe')
            iframe.style.height = event.data.height + 'px'
            break
        default:
            console.log('Pandium event:', message, tenant)
    }
})
```

#### Tenant Payload Shape (When Included)

```
{
  "message": "pandium-install-save",
  "tenant": {
    "id": 123,
    "name": "tenant-name",
    "integration": {
      "id": 456,
      "name": "integration-name"
    }
  }
}
```

#### Supported Events

| Event                                 | Screen              | Trigger                              | Payload    |
| ------------------------------------- | ------------------- | ------------------------------------ | ---------- |
| `pandium-install-cancel`              | Install flow        | Cancel new install  (deletes tenant) | { tenant } |
| `pandium-install-next`                | Install flow        | Next button — advance to next tab    | { tenant } |
| `pandium-install-back`                | Install flow        | Back button — go to previous tab     | { tenant } |
| `pandium-install-save`                | Install flow        | Save without syncing                 | { tenant } |
| `pandium-save-sync`                   | Install flow        | Save & Sync Now — succeeded          | { tenant } |
| `pandium-save-sync-error`             | Install flow        | Save + Sync Now — sync failed        | { tenant } |
| `pandium-connection-settings-cancel`  | Connection Settings | Settings Cancelled                   | { tenant } |
| `pandium-connection-settings-save`    | Connection Settings | Settings Saved                       | { tenant } |
| `pandium-uninstall`                   | Tenant Settings     | Uninstall Confirmed                  | { tenant } |
| `pandium-uninstall-cancel`            | Tenant Settings     | Uninstall Cancelled                  | { tenant } |
| `pandium-connector-connect`           | Connector auth      | Connector Connected                  | { tenant } |
| `pandium-connector-connect-cancel`    | Connector auth      | Connect Cancelled                    | { tenant } |
| `pandium-connector-connect-next`      | Connector auth      | Next in connect flow                 | { tenant } |
| `pandium-connector-connect-back`      | Connector auth      | Back in connect flow                 | { tenant } |
| `pandium-connector-disconnect`        | Connector Auth      | Connector Disconnected               | { tenant } |
| `pandium-connector-disconnect-cancel` | Connector Auth      | Disconnect Cancelled                 | { tenant } |
| `pandium-content-height-change`       | All                 | iFrame content resizes               | { height } |
| `pandium-connect-app`                 | Integration Show    | 'Connect' button clicked             |            |
| `pandium-all-integrations`            | Integration Show    | "All Integrations" nav               |            |


# Public Gallery

Showcase integrations with Pandium’s Public Gallery, a customizable partner directory that supports SEO, white-label branding, hero banners, and embedded lead capture forms.

Pandium's Public Gallery is a highly customizable marketplace offering additional options, such as lead generation forms, improved SEO, and additional white-labeling features. It provides all the tools you need to build a directory of your technology partners and seamlessly embed it on your marketing site.

### Gallery Settings

In the Gallery tab, you'll find customization options tailored to the gallery marketplace, if enabled with Pandium. You can set the tab title, add optional banners, and create headers and footers, all easily customizable directly with HTML and CSS in the admin dash. Pandium also allows you to embed custom HubSpot forms directly into marketplace tiles.

This section contains generic Gallery settings, currently including only the page title, which appears on the browser tab of your Gallery page.

<figure><img src="/files/FfikuecGy4oVnP0W4BBh" alt="" width="445"><figcaption></figcaption></figure>

### Hero Banner Configuration

The Hero Banner is content displayed at the top of your Gallery page. Pandium enables extensive customization using custom HTML and CSS. Enabling this setting reveals two text fields and a component uploader. The first field is for inputting the HTML of your banner configuration, while the second is for holding the CSS of the banner. The Hero Image upload component allows you to directly upload an image for use within the Hero Banner configuration.

<figure><img src="/files/Jsx013FUHGZxi5MfHAsR" alt="" width="563"><figcaption></figcaption></figure>

### Hero Footer Configuration

The Hero Footer Configuration is used to set up a banner on the lower part of the body of the page. The Hero Footer acts much the same as the Hero Banner - toggling this setting reveals two fields where custom HTML and CSS can be input to edit this banner.

<figure><img src="/files/ZQFqmXCVOvvkXkbzytYs" alt="" width="563"><figcaption></figcaption></figure>

### HubSpot Form

Pandium offers the ability to embed your custom HubSpot form directly into the marketplace integration detail pages. Here, you'll input a heading, which is the CTA text the form will display. You'll need a Portal ID and Form ID from HubSpot, and then you'll see another field where you can input your CSS to customize how this form looks on the page.

We also allow for embedding of certain tags within the form, which can be used to auto-populate contact information in HubSpot when setup.

<figure><img src="/files/SgHhHotGe3tNXb5cYxdY" alt="" width="449"><figcaption><p><em>*HubSpot forms require Portal ID and Form ID from HubSpot</em></p></figcaption></figure>

*Note: The In-App Marketplace and related features are not included in the Pandium Lite offering.*


# App Installation Options

Overview of the different ways to surface integrations to your users, including the In-App Marketplace, Live Link, and Auth Link.

Once an integration is created using Pandium, there are several ways to surface it to your users for installation.

| Option                                                                                | Best For                                                             | Requires Marketplace? |
| ------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------- |
| [Auth Link](https://docs.pandium.com/marketplaces/app-installation-options/auth-link) | Sending users directly to authenticate a connector                   | No                    |
| [In-App Marketplace](#in-app-marketplace)                                             | Public discovery and self-service installation                       | Yes                   |
| [Live Link](https://docs.pandium.com/marketplaces/app-installation-options/live-link) | Directing users to a specific integration (published or unpublished) | Yes                   |

## In-App Marketplace

The [In-App Marketplace](https://www.pandium.com/in-app-marketplace) is Pandium's embedded marketplace where your users can discover, install, and manage integrations independently.

### Publishing an Integration

You can publish an integration to your marketplace in two ways:

**Option 1: During integration creation**

While [creating an integration](https://docs.pandium.com/integration-hub/creating-an-integration), navigate to "Marketplace Setup."

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

**Option 2: From an existing integration**

Click "Details" on any integration within the "Integrations" tab.

<figure><img src="/files/1VTQpdlDkNVk8NMnzP7x" alt="" width="384"><figcaption></figcaption></figure>

Within "Marketplace Setup," toggle the publish setting on or off.

<figure><img src="/files/ytJA7TQaxGwKfRuRPl6A" alt=""><figcaption><p>Example: A Grey Wizard Tech (GWT) integration with enabled marketplace publish settings.</p></figcaption></figure>

### End-User Experience

Once published, users can discover and install the integration from your marketplace.

<figure><img src="/files/OHUnyBaKVaWpg3EgKcKP" alt=""><figcaption><p>End-user perspective: A Grey Wizard Tech customer integrating with Google Sheets via the In-App Marketplace.</p></figcaption></figure>

## Choosing Between Auth Link and Live Link

Use [**Auth Link**](/marketplaces/app-installation-options/auth-link) when you want to:

* Bypass the marketplace entirely
* Send users directly to an OAuth or credential entry flow
* Allow users to authenticate without accessing the Integration Hub

Use [**Live Link**](/marketplaces/app-installation-options/live-link) when you want to:

* Direct users to a specific integration page in your marketplace
* Share access to an unpublished or beta integration
* Keep users within the standard marketplace installation flow


# Auth Link

Use Auth Link to send users directly to an authentication flow without requiring the In-App Marketplace.

Auth Link lets you send users directly to an authentication flow for a specific connector, bypassing the In-App Marketplace entirely. This is useful for:

* **OAuth integrations**: Users authenticate directly with a third-party service
* **Credential-based connections**: Users enter their credentials without sharing them with you
* **Streamlined onboarding**: Skip the marketplace when you already know which integration a user needs

## Requirements

* A [Tenant](https://docs.pandium.com/integration-hub/creating-a-tenant) must be created for the user

**Note**: Auth Link does not require the In-App Marketplace to be set up.

## How to Generate an Auth Link

### Step 1: Create or find the tenant

Go to the **Tenants** resource in the left menu bar.

<figure><img src="/files/LJADidugjYPthDsz71Ra" alt="" width="340"><figcaption></figcaption></figure>

If you haven't already, [create a Tenant](https://docs.pandium.com/integration-hub/creating-a-tenant) for your customer.

### Step 2: Open the connector

Click on the tenant to view its details. Under the connector you want the user to authenticate, click **Connect** (or **Reconnect** if previously connected).

<figure><img src="/files/1cygFCdGBEubjYaBxHkb" alt="" width="216"><figcaption></figcaption></figure>

### Step 3: Generate the Auth Link

In the popup window, click **Generate Customer Link**.

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

Copy the link to share with your user.

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

### Step 4: User authenticates

Send the link to your user. They will be taken directly to the authentication process—either an OAuth flow or a credential entry form, depending on the connector.

### Step 5: Verify connection

Once the user completes authentication, their connector status will show a green check on the **Tenant Details** page.

<figure><img src="/files/fqmYNbV0kdlW4ZKkZYWE" alt="" width="320"><figcaption></figcaption></figure>

## Auth Link vs Live Link

| Feature                             | Auth Link        | Live Link          |
| ----------------------------------- | ---------------- | ------------------ |
| Requires marketplace                | No               | Yes                |
| User sees marketplace UI            | No               | Yes                |
| Requires tenant to be created first | Yes              | No                 |
| Scope                               | Single connector | Entire integration |

If you want users to go through the standard marketplace experience, use [Live Link](/marketplaces/app-installation-options/live-link) instead.

## Auth-Only Connections

If you're building a custom marketplace using Pandium's Auth Dialog process, authentication can be defined directly within the JWT. See [Embedding Auth-Only Connections](https://docs.pandium.com/marketplaces/embedding-auth-only-connections) for details.


# Live Link

Use Live Link to direct users to a specific integration in your marketplace, even if the integration is unpublished.

Live Link lets you share a direct URL to a specific integration in your In-App Marketplace. This is useful for:

* **Testing**: Share access to an integration before publishing it
* **Beta programs**: Give select users access to integrations in development
* **Direct navigation**: Skip the marketplace browse experience and send users straight to a specific integration

## Requirements

* Your [In-App Marketplace](https://docs.pandium.com/marketplaces/embedding-the-marketplace) must be set up
* Your marketplace base URL must be configured in Settings

## How to Generate a Live Link

### Step 1: Configure your marketplace URL

Go to **Settings** in the left menu bar under the **Company** tab and ensure your marketplace base URL is saved.

<figure><img src="/files/a1urlT2M3vhyGzob5BDJ" alt=""><figcaption><p>Marketplace URL setting within the "Company" tab under "Settings"</p></figcaption></figure>

### Step 2: Get the Live Link

Navigate to the **Integrations** resource in the Integration Hub and find the integration you want to share.

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

Click the three dots on the integration tile and select **Live Link**.

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

### Step 3: Share the link

Copy the link and send it to your users. They will be taken directly to the integration page within your marketplace.

## What Users See

When users click a Live Link, they land on the integration's detail page in your marketplace. From there, they proceed with the standard installation flow—configuring settings, authenticating, and enabling the integration.

The experience is identical to browsing to the integration manually, except users skip the discovery step.

## Live Link vs Auth Link

| Feature                             | Live Link | Auth Link             |
| ----------------------------------- | --------- | --------------------- |
| Requires marketplace                | Yes       | No                    |
| User sees marketplace UI            | Yes       | No                    |
| Works with unpublished integrations | Yes       | N/A (connector-based) |
| Requires tenant to be created first | No        | Yes                   |

If you need to bypass the marketplace entirely and send users directly to an authentication flow, use [Auth Link](/marketplaces/app-installation-options/auth-link) instead.

*Note: This feature is not included in the Pandium Lite offering.*


# Listing in External Marketplaces

Sometimes there may be instances where you would want your application officially listed in a third-party marketplace. App-Marketplace Ready Connectors make this easier.

## App Marketplace-Ready Connectors <a href="#app-marketplace-ready-connectors" id="app-marketplace-ready-connectors"></a>

App Marketplace-Ready Connectors are pre-configured to meet the technical requirements that major app marketplaces demand for listing approval.

### What Are App Marketplace-Ready Connectors? <a href="#what-are-app-marketplace-ready-connectors" id="what-are-app-marketplace-ready-connectors"></a>

Pandium connectors now come pre-configured with the technical requirements needed to get your integrations listed in major app marketplaces. This means your integrations are automatically built to meet marketplace standards without your team needing to parse through extensive technical documentation or implement custom compliance requirements.

### How App Marketplace-Ready Connectors Work <a href="#how-app-marketplace-ready-connectors-work" id="how-app-marketplace-ready-connectors-work"></a>

When you build integrations using App Marketplace-Ready Connectors, the connector automatically adheres to the technical standards required by the marketplace:

**Pre-configured authentication mechanisms** handle complex OAuth flows and token management automatically, conforming to each marketplace's specific requirements.

**White-labeled UI components** are designed to meet marketplace display standards out of the box.

**Security protocols** align with platform-specific requirements for data handling and user permissions.

**Webhook infrastructure** supports the event handling patterns each marketplace expects.

Pandium's code-first infrastructure enables you to create truly native integrations that meet the quality standards larger marketplaces set for "preferred" or "verified" status, increasing your visibility and credibility in their ecosystems.

### Available Marketplace-Ready Connectors <a href="#available-marketplace-ready-connectors" id="available-marketplace-ready-connectors"></a>

App Marketplace-Ready Connectors are currently available for the following platforms:

* Klaviyo
* BigCommerce
* Yotpo
* Wix

Additional marketplace-ready connectors are continuously being added to the platform. If you need a marketplace-ready connector that isn't currently available, contact your Technical Account Manager.

### Getting Started <a href="#getting-started" id="getting-started"></a>

App Marketplace-Ready Connectors are available for all Pandium customers. To get started building integrations with marketplace-ready connectors:

* Navigate to the Integrations resource in your Integration Hub and create a new integration.
* Select the marketplace-ready connector for your target platform when configuring your connectors.
* Build your integration following Pandium's standard integration development process.

For detailed guidance on meeting the specific requirements for your target marketplace and optimizing your integration for maximum approval success, refer to the marketplace-specific connector documentation or contact your Technical Account Manager.

*Note: Individual marketplace listing processes and submission requirements vary by platform. Consult the documentation for your specific target marketplace for complete listing requirements beyond technical implementation.*


# BigCommerce Marketplace

Learn how to list and manage your Pandium-powered integration on the BigCommerce Marketplace, including configuration steps, listing requirements, and best practices for a successful app listing.

When submitting your app to the BigCommerce marketplace, you'll need to provide two critical URLs:

### 1. Auth Installation URL

The OAuth Installation URL initiates the connection flow when users install your app from the BigCommerce marketplace.

**Base URL Format**

Choose the appropriate base URL for your environment:

**Sandbox Environment:**

{% code overflow="wrap" %}

```
https://exmart.sandbox.pandium.com/v1/bigcommerce/auth/<account_name>/<integration_name>
```

{% endcode %}

**Production Environment:**

{% code overflow="wrap" %}

```
https://exmart.pandium.io/v1/bigcommerce/auth/<account_name>/<integration_name>
```

{% endcode %}

Replace `<account_name>` and `<integration_name>` with your specific values.

### 2. Load URL

The Load URL is used to route to the run logs for the user's tenant in their marketplace after the application is installed.

**URL Format**

```
https://exmart.pandium.io/v1/bigcommerce/marketplace/<account_name>/<integration_name>
```

### 3. Uninstall URL

The purpose of this URL will allow users to disconnect and uninstall tenants from Pandium after they disconnect their app in BigCommerce.

{% code overflow="wrap" %}

```
https://exmart.pandium.io/v1/bigcommerce/uninstall/<account_name>/<integration_name>
```

{% endcode %}

### Need Help?

If you encounter issues during the listing process, please feel free to contact your Pandium Technical Account Manager for assistance.


# Klaviyo Marketplace

Learn how to list and manage your Pandium-powered integration on the Klaviyo Marketplace, including configuration steps, listing requirements, and best practices for a successful app listing.

When submitting your app to the Klaviyo marketplace, you'll need to provide two critical URLs:

### 1. OAuth Installation URL

The OAuth Installation URL initiates the connection flow when users install your app from the Klaviyo marketplace.

**Base URL Format**

Choose the appropriate base URL for your environment:

**Sandbox Environment:**

```
https://exmart.sandbox.pandium.io/v1/connect/<account_name>/<integration_name>
```

**Production Environment:**

```
https://exmart.pandium.io/v1/connect/<account_name>/<integration_name>
```

Replace `<account_name>` and `<integration_name>` with your specific values.

#### Query Parameters

Add the following query parameters to your OAuth Installation URL:

**Connector** (required)

* This is used to determin which connectors will be linked and in what order
* Add multiple `connector` parameters to connect services sequentially.
  * Example: `?connector=magento&connector=Klaviyo` means this will connect Magento first and then Klaviyo

**landing\_url** (required)

* Specifies where users are redirected after completing the installation
* Pandium will append additional query parameters to handle tenant linking
* Format: `?landing_url=<marketplace_url>`
  * Example: `?landing_url=https://webstage.magento.dev/app/merchant/#/Integrations/Store`

### 2. Settings URL

The Settings URL is used to route to the user's tenant settings after the application is installed.

**Format**

Add the `display=settings` query parameter to your marketplace URL:

```
<marketplace_url>?display=settings
```

**Example**

{% code overflow="wrap" %}

```
https://gwtech.io.pandium.com/integrations?integration_id=1121&display=settings
```

{% endcode %}

If you want the tenant list page for your integration, then either omit the display param (since this is default behavior) or can use `display=list`

{% code overflow="wrap" %}

```
https://gwtech.io.pandium.com/integrations?integration_id=1121
```

{% endcode %}

or

```
https://gwtech.io.pandium.com/integrations?integration_id=1121?display=list
```

### Need Help?

If you encounter issues during the listing process, please feel free to contact your Pandium Technical Account Manager for assistance.


# Shopify Marketplace

Learn how to list and manage your Pandium-powered integration on the Shopify Marketplace, including configuration steps, listing requirements, and best practices for a successful app listing.

When submitting your app to the Shopify marketplace, you'll need to include several critical values during the app creation process in your [Shopify Dev Portal](https://dev.shopify.com/).

### 1. Scopes

Please note, the scopes you select during app creation determine which webhooks you can use. These scopes also need to match when you [provision](https://docs.pandium.com/integration-hub/pandium-quick-start#creating-an-internal-integration) the Shopify connector in Pandium.

### 2. Redirect URLs

You would use the following URLs:<br>

* **Production:** <https://api.pandium.io/v0/author/callback/oauth2>
* **Staging:** <https://api.sandbox.pandium.com/v0/author/callback/oauth2>

You also need to make sure you include your Landing URL in your redirect URLs.

### 3. App URLs

The OAuth Installation URL initiates the connection flow when users install your app from the Shopify marketplace. This URL is configured in the Shopify Dev Portal and is called by Shopify when a user installs your app.

{% hint style="warning" %}
**Important:** This URL cannot be accessed directly. It must be called by Shopify, which includes HMAC signature verification parameters. Accessing this URL directly will result in a 401 Unauthorized error.
{% endhint %}

**Base URL Format**

Choose the appropriate base URL for your environment:

**Sandbox Environment:**

{% code overflow="wrap" %}

```
https://exmart.sandbox.pandium.com/v1/shopify/auth/<account_name>/<integration_name>
```

{% endcode %}

**Production Environment:**

{% code overflow="wrap" %}

```
https://exmart.pandium.io/v1/shopify/auth/<account_name>/<integration_name>
```

{% endcode %}

Replace `<account_name>` and `<integration_name>` with your specific values.

#### Query Parameters

Add the following query parameters to your App URL:

**landing\_url** (required)

* Specifies where users are redirected after completing the installation
* Pandium will append additional query parameters to handle tenant linking
* Format: `?landing_url=<marketplace_url>`
  * Example: `?landing_url=https://gwtech.io..pandium.com/integrations`

{% hint style="info" %}
Reminder: Please include your landing URL in your redirect URLs
{% endhint %}

### 4. Uncheck "Embedded App in Shopify Admin"

At this time, Pandium doesn't doesn't support this feaure, so you need to make sure this is unchecked before submitting your application.

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

### Uninstalling Your Shopify App

By default, we auto subscribe each shop that installs the integration to the Shopify uninstall webhooks. As a result, when you uninstall it from the Shopify dashboard, this will uninstall/archive the corresponding tenant in Pandium.\
\
When you uninstall your tenant from your Pandium marketplace, this will result in the app being uninstalled from the Shopify Dashboard.

### Need Help?

If you encounter issues during the listing process, please feel free to contact your Pandium Technical Account Manager for assistance.


# Wix Marketplace

Learn how to configure the required External URL, App URL, and Redirect URL to submit your app to the Wix Marketplace using Pandium’s custom authentication flow.

When submitting your app to the Wix marketplace, you'll need to provide 3 critical URLs if using the custom authentication option:

### 1. External URL

The external URL extension allows you display an embedded instance of your installed app inside the Wix dashboard.

{% code overflow="wrap" %}

```
https://exmart.pandium.io/v1/wix/dashboard/<account_name>/<integration_name>
```

{% endcode %}

Replace `<account_name>` and `<integration_name>` with your specific values.

### 2. App URL

This App URL is used to initiate an integration install from the Wix Dashboard to kick off the Oauth flow:

```
https://exmart.pandium.io/v1/wix/auth/<account_name>/<integration_name>
```

The URL will most likely resemble the above, but simply use this URL for the 'Install' page of this App in your Marketplace.

### 3. Redirect URL

When users authorize your app, Wix will redirect them to this URL with a temporary authorization code.

**Sandbox**

```
https://api.sandbox.pandium.com/v0/author/callback/oauth2
```

```
https://exmart.sandbox.pandium.com/v1/wix/redirect/<account_name>/<integration_name>
```

**Production**

```
https://api.pandium.io/v0/author/callback/oauth2
```

```
https://exmart.pandium.io/v1/wix/redirect/<account_name>/<integration_name>
```

### Need Help?

If you encounter issues during the listing process, please feel free to contact your Pandium Technical Account Manager for assistance.


# Yotpo Marketplace

Learn how to submit your app to the Yotpo Marketplace by configuring the required Start Install URL and Redirect URL for a smooth installation and authentication flow from Yotpo into your product.

When submitting your app to the Yotpo marketplace, you'll need to provide two critical URLs:

### 1. Start Install URL

The Start Install URL will be used for installs that are initiated in the Yotpo admin or the Yotpo integrations page. When clicking “connect”, the user will be redirected to your admin to start the installation flow.

```
https://<marketplace_site_address>/<integration_name>/integrations
```

The URL will most likely resemble the above, but simply use the URL for the 'Install' page of this App in your Marketplace.

### 2. Redirect URL

The Redirect URL is used to redirect back to the page within your product/platform as part of the authentication flow. Each app can have only one redirect URL, but a developer can have several applications, either for integrating with different Yotpo products or for testing & production. We recommend creating two: one for sandbox and one for production.

**Sandbox**

```
https://api.sandbox.pandium.com/v0/author/callback/oauth2
```

**Production**

```
https://api.pandium.io/v0/author/callback/oauth2
```

### Uninstall Webhook Requirements

If you are using the Pandium embedded marketplace, we'll manage all webhook uninstall requirements on your behalf. If you are using your own marketplace, then we will send you a webhook to uninstall the application.

### Need Help?

If you encounter issues during the listing process, please feel free to contact your Pandium Technical Account Manager for assistance.


# Connectors 101

Pandium connectors securely manage encrypted auth secrets for integrations and tenants, support multiple auth methods, and power hundreds of native connections without wrapping third-party APIs.

**What are Connectors?**\
\
A connector serves as an integral component within an integration, allowing Pandium to securely provide the required encrypted secrets or tokens to the specific integration and tenant combination. Pandium's connectors are designed to accommodate the authentication requirements of partner systems, whether that involves an OAuth protocol, a simple API key, or a custom and proprietary method.

In contrast to many integration platforms, Pandium’s connectors focus solely on authorization, authentication, and receiving webhooks. It's important to note that Pandium's connectors refrain from interacting with external APIs beyond these essential functions. As such, there is no “Pandium version” or “wrapper” around third-party APIs.

**Connector Secrets?**

When an integration script runs on the platform, it dynamically accepts the necessary secrets as environment variables at runtime. These secrets are encrypted at rest and in transit, and cannot be exported.

For a given integration, connectors can be set on a per-tenant basis or globally. This global configuration is particularly useful when one facet of the integration remains static, such as utilizing a uniform SFTP or cloud storage bucket for all customer accounts. Opting for a global connector implies that authentication into the system is required only once. Subsequently, all created tenants will share the same set of global secrets, eliminating the need for redundant authentication.

Pandium connections can be diverse - we don't need to connect to a public API to work. Whether it's through public or private APIs (leveraging a direct AWS/GCP/Azure connection), SFTP, Storage Buckets, or even direct database connections, Pandium adapts seamlessly, provided the necessary access is available.

**How many native connectors does Pandium Support?**

210! That's a lot of connections; for a full list, please see [here](https://www.pandium.com/connectors).\
\
We are always evolving as your business grows, so if there is an integration that your tech stack requires and you do not see a connector for it, please reach out to your Technical Account Manager about getting one built on your timeline at no additional cost.\
\
For setup configuration guides, please refer to the linked sub-pages for our existing connectors.

**How do your connectors differ from one another?**\
\
They don't because we don't wrap the API. Our connectors only operate as an authentication management tool. The only difference between connectors is how the SaaS provider authenticates their respective API.

#### Redirect URLs

For integrations that are OAuth-based apps, when a redirect URL is requested, please enter the following information:\
\
Sandbox/Staging - <https://api.sandbox.pandium.com/v0/author/callback/oauth2>\
\
Production - <https://api.pandium.io/v0/author/callback/oauth2>


# Active Campaign

Configure the ActiveCampaign connector in Pandium by collecting your account name, API key, webhook events, and sources so tenants can authenticate and sync data securely at scale.

## **Connector Overview**

<figure><img src="/files/vCU6dOXbABew0F6HNo2e" alt="" width="188"><figcaption></figcaption></figure>

ActiveCampaign is a cloud-based platform that combines email marketing, automation, and CRM to streamline processes, enhance customer relationships, and drive data-driven growth.

## **Authentication Type**

Basic

## **Webhook Supported**

Yes

## **Secrets**

* PAN\_SEC\_ACTIVECAMPAIGN\_API\_KEY
* PAN\_SEC\_ACTIVECAMPAIGN\_ACCOUNT\_NAME
* PAN\_SEC\_ACTIVECAMPAIGN\_WEBHOOK\_EVENTS
* PAN\_SEC\_ACTIVECAMPAIGN\_WEBHOOK\_SOURCES

## **API Client Supported**

Yes

## **Requirements for Provisioning**

In order to provision your connector, the following Active Campaign information must be gathered:

* Webhook events
* Webhook sources

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to ActiveCampaign:

* Account Name
* API Key

To obtain the information required for setting up, perform the following steps:

1. Log into your Acive Campaign [admin account](https://www.activecampaign.com/login)
2. Navigate to Settings > Developer
3. You API Key should be located under API Access:\
   ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXe152rxqzSB-Ls2IXTkSdocA2akjo_UO1b0ZtuO84sWgqoRXm-8OQbYTuXyNQYxLuJr47p60FynGVlrzA_TXp21UBuZORK2uVGDaJS7beLo2nhGZ4r1mghAVwPDCk5XL7anuzSR?key=XIy8122ZNqmu67sUe4Q2WNF3)
4. Your Account Name can be found in the your URL. It is the address followed by “.activehosted.com”
   1. Example - 123456demo.activehosted.com

*For additional information, please see* [*here*](https://developers.activecampaign.com/reference/authentication)*.*

## **API Resources**

For more information on how to utilize the ActiveCampaign API, feel free to reference the following [documentation](https://developers.activecampaign.com/reference/authentication).


# Afterpay

Set up the Afterpay connector in Pandium by using your Merchant ID and Secret Key so tenants can authenticate and process BNPL payments securely without extra provisioning.

## **Connector Overview**

<figure><img src="/files/n48siI952jKnU1h3xEyI" alt="" width="153"><figcaption></figcaption></figure>

Afterpay is a buy now, pay later (BNPL) platform that lets customers purchase items, receive them immediately, and pay in interest-free installments, making shopping more flexible and boosting sales for businesses.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AFTERPAY\_MERCHANT\_ID
* PAN\_SEC\_AFTERPAY\_SECRET\_KEY

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Afterpay:

* Merchant ID
* Secret Key

To obtain the information required for connecting, perform the following steps:

1. Log in to your [Merchant Account](https://hub.us.afterpay.com/us)
2. Once logged in, select Settings in the lefthand sidebar menu. Please refer to the screenshot for guidance:\
   ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcFtEp_NLXw6PM60SOAuVjp0wBo6CiQx-twICDnVnDPRTEBvTdTImHfdyNVK-OU66RRJSiHM9KwkC3G5yy8lOVXtHazDB0H7upitAGbwYz_GWECYziuhtIsTJGtXavyETGlTa4?key=XIy8122ZNqmu67sUe4Q2WNF3)
3. Under Settings, please find the section displaying your Merchant ID and Secret Key.\
   ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXeBvXvX8lQRyhZMfnFVm59LU9djpe4efHOpgNKz-6b_f5b_XZwABYNkFtuJ9XiuKqSNVzPwp7uyVbMlV4tOwJa01X40_vvOcpBAtfFYjsdeaZUwJAfn8wfqGH1Gh-8v41z3m-AjPg?key=XIy8122ZNqmu67sUe4Q2WNF3)

*If you have any issues accessing your merchant ID or secret key, please reach out to* [*Afterpay Merchant Support*](https://developers.afterpay.com/docs/api/merchant-operations/support/merchant-support)

## **API Resources**

For more information on how to utilize the Afterpay API, feel free to reference the following [documentation](https://developers.afterpay.com/docs/api/welcome/getting-started).


# AfterShip

Set up the AfterShip connector in Pandium using your API key so tenants can authenticate securely and sync shipment tracking data without any additional provisioning.

## **Connector Overview**

<figure><img src="/files/14DxlA3UE1joH7hUpeBK" alt="" width="215"><figcaption></figcaption></figure>

AfterShip is a shipment tracking solution that helps eCommerce businesses keep customers informed with real-time updates, automated notifications, and easy returns for a smoother shopping experience

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AFTERSHIP\_API\_KEY

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Aftership:

* API Key

To obtain the information required for connecting, perform the following steps:

1. Log in to your [Aftership Admin Account](https://accounts.aftership.com/)
2. Navigate to Developers > API Keys\
   ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXd7RWHgzzauikz1kBw2n94-8LBNUcFLLvChusN8abB6e7NcePdGIk04vefHpDCNJJ1Vizhxw2QdCyMnN0Ovq86MiMMhJp2aNEXRUKMjnVoe4q7kesYClKh5PPdhZifpjXpfvZ94vw?key=XIy8122ZNqmu67sUe4Q2WNF3)
   1. If you don’t see a key present, select Create API Key

*For more information on how to generate an API Key, please see* [*here*](https://support.aftership.com/en/tracking/article/how-to-generate-aftership-api-key-12cuqj2#3-how-to-generate-an-api-key)*.*

## **API Resources**

For more information on how to utilize the Aftership API, feel free to reference the following [documentation](https://www.aftership.com/docs).<br>


# Airship

Set up the Airship connector in Pandium by configuring your API base URL and scopes so tenants can authenticate securely and power personalized mobile engagement.

## **Connector Overview**

<figure><img src="/files/3eZKH53dBzCJznEz9X0K" alt=""><figcaption></figcaption></figure>

Airship is a customer engagement platform that helps businesses manage personalized mobile messaging, notifications, and marketing automation to increase user retention.

## **Authentication Type**

OAuth

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AIRSHIP\_ACCESS\_TOKEN
* PAN\_SEC\_AIRSHIP\_SCOPE
* PAN\_SEC\_AIRSHIP\_EXPIRES\_IN
* PAN\_SEC\_AIRSHIP\_TOKEN\_TYPE
* PAN\_SEC\_AIRSHIP\_CLIENT\_ID
* PAN\_SEC\_AIRSHIP\_CLIENT\_SECRET
* PAN\_SEC\_AIRSHIP\_API\_KEY
* PAN\_SEC\_AIRSHIP\_TENANT\_URL
* PAN\_SEC\_AIRSHIP\_API\_BASE\_URL

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Airship information must be gathered:

* API Base URL
* Scopes

## **API Resources**

For more information on how to utilize the Airship API, feel free to reference the following [documentation](https://docs.airship.com/api/ua/).<br>


# Alasco

Set up the Alasco connector in Pandium using your API key and API token so tenants can authenticate securely and sync construction financial data without extra provisioning.

## **Connector Overview**

<figure><img src="/files/RDKjwYGnZDNP0urBwB0o" alt="" width="377"><figcaption></figcaption></figure>

Alasco is a construction financial management platform that helps project teams manage budgets, track expenses, and streamline financial workflows to ensure projects stay on track and on budget.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ALASCO\_API\_KEY
* PAN\_SEC\_ALASCO\_API\_TOKEN

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Alasco:

* API Key
* API Token

To obtain the information required for connecting, perform the following steps:

1. Log into

## **API Resources**

For more information on how to utilize the Alasco API, feel free to reference the following [documentation](https://developer.alasco.de/).


# Algolia

Set up the Algolia connector in Pandium with your Application ID and API keys so tenants can authenticate securely and sync search and discovery data across your apps.

## **Connector Overview**

<figure><img src="/files/8gPBY2j1zzQPzhi3wlIY" alt="" width="377"><figcaption></figcaption></figure>

Algolia is a search and discovery API for developing product search and discovery experiences on websites and mobile apps, offering speed, relevance, and a rich user experience.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ALGOLIA\_APPLICATION\_ID
* PAN\_SEC\_ALGOLIA\_SEARCH\_ONLY\_API\_KEY
* PAN\_SEC\_ALGOLIA\_ADMIN\_API\_KEY
* PAN\_SEC\_ALGOLIA\_USAGE\_API\_KEY
* PAN\_SEC\_ALGOLIA\_MONITORING\_API\_KEY

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Algolia:

* Application ID
* Search-Only API Key
* Admin API Key
* Usage API Key
* Monitoring API Key

To obtain the information required for connecting, perform the following steps:

1. Log into

## **API Resources**

For more information on how to utilize the Algolia API, feel free to reference the following [documentation](https://www.algolia.com/doc/api-reference/rest-api/).


# Amadeus

Set up the Amadeus OAuth connector in Pandium using your API key, API secret, and token URL so tenants can authenticate securely and sync travel booking data at scale

## **Connector Overview**

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

Amadeus is a travel technology platform that helps airlines, hotels, and travel agencies streamline bookings, manage inventory, and enhance customer experiences across the global travel industry.

## **Authentication Type**

OAuth

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AMADEUS\_OAUTH\_CLIENT\_ID
* PAN\_SEC\_AMADEUS\_OAUTH\_CLIENT\_SECRET
* PAN\_SEC\_AMADEUS\_OAUTH\_ACCESS\_TOKEN

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Amadeus information must be gathered:

* API Key
* API Secret
* Token URL

## **API Resources**

For more information on how to utilize the Amadeus API, feel free to reference the following [documentation](https://developers.amadeus.com/self-service/apis-docs/guides/developer-guides/).


# Amazon

Set up the Amazon OAuth2 connector in Pandium using your app ID, client credentials, and seller marketplace region so tenants can authenticate and sync eCommerce data securely.

## **Connector Overview**

<figure><img src="/files/3dn9sgN9NJCgp5LigsOn" alt="" width="377"><figcaption></figcaption></figure>

Amazon is an eCommerce and cloud computing giant that provides a wide range of services including online retail, cloud storage, and artificial intelligence tools.

## **Authentication Type**

OAuth2

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AMAZON\_APP\_ID
* PAN\_SEC\_AMAZON\_CLIENT\_IDENTIFIER
* PAN\_SEC\_AMAZON\_CLIENT\_SECRET
* PAN\_SEC\_AMAZON\_APPLICATION\_IN\_DRAFT\_

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Amazon information must be gathered:

* App ID
* Client identifier
* Client secret
* Application In Draft?

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Amazon:

* Amazon Seller Marketplace Region

## **API Resources**

For more information on how to utilize the Amazon API, feel free to reference the following [documentation](https://aws.amazon.com/api-gateway/).


# Ankored

Set up the Ankored OAuth2 connector in Pandium using your client ID, credentials, scopes, and identity API URL so tenants can sync inventory and shipping data securely at scale.

## **Connector Overview**

<figure><img src="/files/r2D5nFcQsJS3fvdVZJGd" alt="" width="377"><figcaption></figcaption></figure>

Ankored is a platform designed to help businesses optimize their inventory management and shipping processes, providing tools to track shipments and streamline fulfillment.

## **Authentication Type**

OAuth2

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ANKORED\_CLIENT\_ID
* PAN\_SEC\_ANKORED\_PASSWORD
* PAN\_SEC\_ANKORED\_USERNAME
* PAN\_SEC\_ANKORED\_ACCESS\_TOKEN
* PAN\_SEC\_ANKORED\_SCOPE
* PAN\_SEC\_ANKORED\_API\_BASE\_URL

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Ankored:

* Client ID
* Username
* Password
* Scopes
* Identity API URL

## **API Resources**

For more information on how to utilize the Aknored API, feel free to reference the following [documentation](https://docs.google.com/document/d/1YWi_R-A9gX-5ZdFElQEVp2jTILL5lXRklWKJ6Oy_4oY/edit?usp=sharing).


# Anthropic

Set up the Anthropic connector in Pandium using your API key and Claude version so tenants can securely power AI features with Claude Sonnet 4 and Claude Opus 4.

## **Connector Overview**

<figure><img src="/files/Juvd8gV0n0XCzSfVEIC7" alt="" width="300"><figcaption></figcaption></figure>

Anthropic is an AI research company that builds reliable systems like Claude, focusing on safety and developing conversational assistants that prioritize helpfulness and human alignment.

## **Supported Models**

Claude Sonnet 4, Claude Opus 4

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ANTHROPIC\_CLAUDE\_API\_KEY
* PAN\_SEC\_ANTHROPIC\_CLAUDE\_VERSION

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Anthropic information must be gathered:

* API Key
* Anthropic Version

To obtain the information required for provisioning the Anthropic Connector, perform the following steps:<br>

1. Log in to your [Anthropic Account](https://console.anthropic.com/)
2. Navigate to Manage > API Keys
3. Select **+Create Key**

## **API Resources**

For more information on how to utilize the Anthropic API, feel free to reference the following [documentation](https://docs.anthropic.com/en/home).


# Apollo.io

Set up the Apollo.io connector in Pandium using your scoped or master API key so tenants can authenticate securely and sync prospecting and sales engagement data.

## **Connector Overview**

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXek3Tqst1fjYeDfhtnXC6gvMj7-uFs-cD75p4FMpm5sOY_Zzt4crV385WV2_4gF-XGZ-Yp0WNOoMR--spEOXAcfx2eRGC_o-hhzBhD9y2xHqvms5R7wuZjpUubYUsQ-sDDwotu-cg?key=XIy8122ZNqmu67sUe4Q2WNF3" alt="" width="375"><figcaption></figcaption></figure>

Apollo.io is a sales engagement platform that combines prospecting, data enrichment, and outreach tools. It empowers businesses to find leads, connect with prospects, and optimize workflows to drive sales and growth effectively.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_APOLLO\_IO\_API\_KEY

## **API Client Supported**

Yes. You can read more about the capabilities of the Integration Development Kit [here](broken://spaces/-MfdKgN0cGTpmF3-1gDd/pages/sJfQYq4MK2Bke6lrfErK).

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Apollo:

* API Key

To obtain the information required for setting up, perform the following steps:

1. Launch Apollo and click Settings > [Integrations](https://app.apollo.io/#/settings/integrations).
2. Find the API option and click Connect.\
   ![alt text](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcId0Y1EGk6W5dhrmZEMqkKQUh3uBbSQl1ZsYH7usZrflHYd1RloTNdL9R9ZECxd_qblPuBZad1-MVfzrE8Zllp3P-dlUBO9F--Wru0VOaUyZHdz2lzuGHvktiqkjzCD2SVactJig?key=XIy8122ZNqmu67sUe4Q2WNF3)
3. Click API keys to view or create new API keys. Then, click Create new key.\
   ![alt text](https://lh7-rt.googleusercontent.com/docsz/AD_4nXdOfZx9cRtgcYJNeqqGMlzO-rcFPI0JLV8yU4gV-b7wvNFsPRAP0zIFb57YwVmFLOAFA-rZ1LYp_iHxIBmNwCYhYs9p-U8aeyjI0PqcCZPMGyziZ3vHPWOrljVnsZfTwifJ903M2w?key=XIy8122ZNqmu67sUe4Q2WNF3)
4. Name your API Key and add a description. To scope your key appropriately, click the checkbox for each Apollo API endpoint you need to access.\
   ![alt text](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcILqybI-xI37FbUcE7P7izqirIqoze7Um0fXxAMVtAiYUHHvqEsGNjzJVooORg396TV3KtiWseA64cHGBtpUOSpjxhULGcGt2qZilcL5ziK4aPSbvB80K9bKjCC7NmotbctNomIw?key=XIy8122ZNqmu67sUe4Q2WNF3)
5. To access all endpoints that are available with your Apollo plan, set your key to be a master key by toggling on the Set as master key slider. Certain endpoints such as [Get a List of Users](https://docs.apollo.io/docs/get-a-list-of-users) are only accessible when using a master API key.\
   ![alt text](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcoUk1ezzcTLqAGbmviLasRKrT4ICedksR9hTDt4E3ulctcBPSeWLuzFtxoXOxb7Gx4s0_CgHA_vwIniBdTZWZp4OxhvneZtj-rHlhZGSyg8DOXovpVgTaIURcl9FKRUL06oHRY?key=XIy8122ZNqmu67sUe4Q2WNF3)
6. Click Create API key.

## **API Resources**

For more information on how to utilize the Apollo.io API, feel free to reference the following [documentation](https://docs.apollo.io/?shell#introduction).


# AppSignal

Set up the AppSignal connector in Pandium using your AppSignal token so tenants can authenticate securely and sync application monitoring and error tracking data.

## **Connector Overview**

<figure><img src="/files/BIe1d61izggLA0FFsp9w" alt="" width="188"><figcaption></figcaption></figure>

AppSignal is an application monitoring and error tracking service designed to help developers and businesses monitor and improve the performance of their applications.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_APPSIGNAL\_TOKEN

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to AppSignal:

* Token

## **API Resources**

For more information on how to utilize the AppSignal API, feel free to reference the following [documentation](https://docs.appsignal.com/api.html).


# AskNicely

Set up the AskNicely connector in Pandium using your domain and API key so tenants can authenticate securely and sync customer feedback and NPS data.

## **Connector Overview**

<figure><img src="/files/CzWIdK9zty5SVMMvTf5M" alt="" width="182"><figcaption></figcaption></figure>

AskNicely is a customer experience platform that helps businesses improve satisfaction and drive growth with real-time feedback and the Net Promoter Score (NPS) framework.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ASKNICELY\_API\_KEY
* PAN\_SEC\_ASKNICELY\_DOMAIN

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to AskNicely:

* API Key
* Domain

To obtain the information required for connecting, perform the following steps:

1. Log in to your [AskNicely Admin Account](https://start.asknice.ly/findlogin/)
2. Navidate to Settings > API > API Keys\
   ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXdzgh6vQJJIxQz7zgtBHGfX5xBZ6eeVKNuzIgTine7TnqBVqpxMXyjFJ9G-SGmaolmDHtRW62M0N04z83oq3u1719FifsbP2Un6vidgBlIq1Db17o0QTvf0zobXWP603sO9NfV9?key=XIy8122ZNqmu67sUe4Q2WNF3)
3. Your domain comes from the URL when you are logged into the app (e.g. yourcompany.asknice.ly).

## **API Resources**

For more information on how to utilize the AskNicely API, feel free to reference the following [documentation](https://asknicely.asknice.ly/help/apidocs).


# Assembled

Set up the Assembled connector in Pandium using your API key so tenants can securely sync AI-powered support operations and omnichannel staffing data.

## **Connector Overview**

<figure><img src="/files/ng5nUBhEL0ND4Dhyl2GA" alt="" width="377"><figcaption></figcaption></figure>

Assembled is the only AI-powered support operations platform built to empower seamless omnichannel support across the entire ecosystem, helping teams staff smarter, increase productivity, and deliver exceptional customer support at any stage of scale.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_ASSEMBLED\_API\_KEY

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to Assembled:

* API Key

## **API Resources**

For more information on how to utilize the Assembled API, feel free to reference the following [documentation](https://docs.assembled.com/#introduction).


# Attentive

Set up the Attentive connector in Pandium using your Client ID and Secret so tenants can authenticate securely and sync personalized SMS and email campaigns.

## **Connector Overview**

<figure><img src="/files/V4J1zQe1kMNkbKSDem6a" alt="" width="377"><figcaption></figcaption></figure>

Attentive is a mobile marketing platform that helps eCommerce businesses connect with customers through personalized SMS and email campaigns, driving engagement and sales.

## **Authentication Type**

OAuth2

## **Webhook Supported**

Yes

## **Secrets**

* PAN\_SEC\_ATTENTIVE\_ACCESS\_TOKEN
* PAN\_SEC\_ATTENTIVE\_TIMESTAMP

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Attentive information must be gathered:

* Client ID
* Client Secret
* Webhook Events

To obtain the information required for provisioning, perform the following steps:

1. Log in to your [Attentive Mobile account](https://ui.attentivemobile.com/signin)
2. Navigate to Setup > Marketplace > Select the app you wish to connect
   1. If you don’t have an app, you can hit “Create App”
3. Edit App > Manage Distribution > The app client ID and client secret should both be listed below
4. For Webhook Events, navigate to Edit App > Webhooks > Select events to post to URL

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will perform the following steps to connect to Attentive:

1. Select Connect
2. Select the URL under Tenant Secrets
3. Authorize\ <br>

## **API Resources**

For more information on how to utilize the Attentive API, feel free to reference the following [documentation](https://docs.attentive.com/).


# AWS

Set up the AWS connector in Pandium using your Access Key ID and Secret Access Key so tenants can authenticate securely and sync cloud resources without extra provisioning.

## **Connector Overview**

<figure><img src="/files/L8dGKmZwRnJsdvt1bqNw" alt="" width="377"><figcaption></figcaption></figure>

Amazon Web Services (AWS) is a comprehensive and widely adopted cloud platform that provides on-demand computing power, storage, and other services for businesses of all sizes.

## **Authentication Type**

Basic

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AWS\_ACCESS\_KEY\_ID
* PAN\_SEC\_AWS\_SECRET\_ACCESS\_KEY

## **API Client Supported**

No

## **Requirements for Provisioning**

No provisioning is required for this connector.

## **How to Connect Your Integration**

Upon successfully creating a tenant, you will use the following information to connect to AWS:

* Access Key ID
* Secret Access Key

## **API Resources**

For more information on how to utilize the AWS S3 API, feel free to reference the following [documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_Reference.html).


# Azure Devops

Set up the Azure DevOps connector in Pandium using your client ID, client secret, and scopes so tenants can authenticate securely and sync DevOps project data.

## **Connector Overview**

<figure><img src="/files/6koYsHQ8hgkwQpZ2Q16l" alt=""><figcaption></figcaption></figure>

Azure DevOps is a cloud-based set of development tools by Microsoft that helps businesses plan, develop, test, and deliver software applications in a collaborative environment.

**Please Note:** Starting April 2025, Microsoft will no longer accept new registrations of Azure DevOps OAuth apps. In order to connect your Azure repository, you will need to register an application using Microsoft Entra. For more information, please see [here](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). For additional information on these recent changes, see [here](https://devblogs.microsoft.com/devops/no-new-azure-devops-oauth-apps/).

## **Authentication Type**

Oauth2

## **Webhook Supported**

No

## **Secrets**

* PAN\_SEC\_AZURE\_DEVOPS\_EXPIRES\_AT
* PAN\_SEC\_AZURE\_DEVOPS\_ACCESS\_TOKEN
* PAN\_SEC\_AZURE\_DEVOPS\_EXPIRES\_IN
* PAN\_SEC\_AZURE\_DEVOPS\_TOKEN\_TYPE
* PAN\_SEC\_AZURE\_DEVOPS\_REFRESH\_TOKEN
* PAN\_SEC\_AZURE\_DEVOPS\_SCOPE
* PAN\_SEC\_AZURE\_DEVOPS\_REDIRECT\_URI

## **API Client Supported**

No

## **Requirements for Provisioning**

In order to provision your connector, the following Azure information must be gathered:

* Client ID
* Client Secret
* Scopes

## **API Resources**

For more information on how to utilize the Azure API, feel free to reference the following [documentation](https://learn.microsoft.com/en-us/rest/api/azure/).




---

[Next Page](/llms-full.txt/1)

