---
agentTools:
  projectIndex: https://percolate.dev/llms.txt
---

# Defining an App

Developers can create 2 main types of apps for Percolate:

> **An App with a UI Extension** - these apps will display a UI Extension within Percolate and visibly integrate with the Percolate workflow. They may or may not access Percolate data.

> **An App without a UI Extension** - these apps do not display a UI within Percolate but require access to Percolate data.

In both cases, the starting point for creating an app is the **App manifest**.

\#Defining your App with an App Manifest

The App manifest is a JSON file describing the characteristics and the requirements of an app.  It is uploaded to register an App to a Percolate account.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"name\": \"Crostini\",\n  \"version\": \"1.1.0\",\n  \"description\": \"A demo for Percolate Developer Platform (PDP)\",\n  \"environment\": \"production\",\n  \"developer\": \"Percolate, Inc.\",\n  \"developer_email\": \"email@example.com\",\n  \"support_website\": \"https://example.com\",\n  \"logo\": \"https://prclt-crostini.herokuapp.com/logo.svg\",\n  \"capabilities\": [\"brief:read\", \"brief:create\", \"brief:edit\", \"brief:delete\"],\n  \"lifecycle\": {\n    \"install\": \"https://prclt-crostini.herokuapp.com/install\",\n    \"uninstall\": \"https://prclt-crostini.herokuapp.com/uninstall\"\n  },\n  \"extensions\": [\n    {\n      \"location\": \"campaign_tab\",\n      \"label\": \"Crostini\",\n      \"url\": \"https://prclt-crostini.herokuapp.com/campaign\"\n    },\n    {\n      \"location\": \"settings\",\n      \"label\": \"Crostini\",\n      \"url\": \"https://prclt-crostini.herokuapp.com/settings\"\n    }\n  ]\n}",
      "language": "json",
      "name": "Example app manifest"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`description`",
    "0-1": "A short description of the app functionality.",
    "1-0": "`requirements` (optional)",
    "1-1": "The technical text listing what the app requires to function properly. For ex: \"Requires admin account permissions to log into the app\"",
    "2-0": "`release_notes` (optional)",
    "2-1": "The description of the reason for uploading the new manifest. A full changelog can be compiled from the release notes of all versions of an application.",
    "3-0": "`logo`",
    "3-1": "A URL pointing to either an image or SVG. The logo should preferably be squared, 128 by 128 pixels with a transparent background.",
    "4-0": "`developer`",
    "4-1": "The developer’s name.",
    "5-0": "`developer_email`",
    "5-1": "The developer’s email.",
    "6-0": "`support_website`",
    "6-1": "The website URL intended for support.",
    "7-0": "`environment`",
    "7-1": "Must be set to “production” for apps intending to be installed to multiple tenants, otherwise “development” if it will only be installed in a single tenant.",
    "8-0": "`version`",
    "8-1": "The [semantic](https://semver.org/) version of the app.",
    "9-0": "`capabilities` (optional)",
    "9-1": "List of Percolate capabilities to give the app access to specific Percolate endpoints and data. Learn how to define capabilities with [this page](https://percolate.dev/docs/capabilities).",
    "10-0": "`extensions` (optional)",
    "10-1": "See defining UI Extensions section below.",
    "11-0": "`lifecycle`  (optional)",
    "11-1": "See defining lifecycle callbacks section below."
  },
  "cols": 2,
  "rows": 12
}
[/block]

\#Defining UI Extensions

A **UI Extension JSON Object** defines where, what, and how an App displays its UI within Percolate.

[block:parameters]
{
  "data": {
    "0-0": "`location`",
    "h-0": "Property",
    "h-1": "Description",
    "0-1": "One of the 7 location ids:\n\n`top_nav` - shows an icon in the global nav dropdown linking to a fullpage UI\n`campaign_tab` - shows a tab on the Campaign Page\n`post_tab` - shows a tab on the Content Page\n`asset_tab` - show a tab on the Asset Page\n`request_tab` - shows a tab on the Request Page\n`task_tab` - shows a tab on the Task Page\n`settings` - show a tab on the Installed App Page",
    "1-0": "`url`",
    "1-1": "The URL for the chosen location. Must use HTTPS.",
    "2-0": "`label`",
    "2-1": "The name of the extension to be displayed.",
    "3-0": "`logo` (optional)",
    "3-1": "A URL pointing to either an image or SVG. The logo should preferably be squared, 128 by 128 pixels with a transparent background."
  },
  "cols": 2,
  "rows": 4
}
[/block]

\#Understanding UI Extension Context

**UI Extensions** are rendered within an `IFRAME` in Percolate. To provide contextual data, a JSON Web Token (`JWT`) assigned to the `jwt` query param is appended to the extension’s `URL`.

**Example**:

```
https://prclt-crostini.herokuapp.com/app/campaign.html?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6.MjM0NTY3ODkwIiwibm.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNr
```

Our `JWT` is signed using the **App Secret** with `HMAC SHA256` algorithm (`HS256`).

Once decoded and verified, the payload, here called **UI Extension Context**, has the following properties (["claims" in JWT parlance](https://auth0.com/docs/tokens/concepts/jwt-claims#reserved-claims)):

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"aud\": \"prclt-crostini.herokuapp.com\",\n  \"exp\": 1572014710,\n  \"iat\": 1572014650,\n  \"iss\": \"percolate.com\",\n  \"jti\": \"e7d52da8-f435-41e9-9d35-c24db67e1d2c\",\n  \"sub\": \"user:123\",\n  \"app_id\": \"app:123456789_123456789\",\n  \"location\": \"campaign_tab\",\n  \"object_id\": \"campaign:123\",\n  \"object_scope_id\": \"license:123\",\n  \"tenant_id\": \"tenant:1\",\n  \"user_id\": \"user:123\"\n}",
      "language": "json",
      "name": "Example of a decoded JWT "
    }
  ]
}
[/block]

**Standard Properties**

Also known as [reserved claims](https://auth0.com/docs/tokens/concepts/jwt-claims#reserved-claims) in the JWT standard.

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`aud`",
    "1-0": "`exp`",
    "2-0": "`iat`",
    "3-0": "`iss`",
    "4-0": "`jti`",
    "0-1": "Audience, the recipient’s domain name the JWT is intended for.",
    "1-1": "Expiration time, after which the JWT must not be accepted for processing. The value is set at exactly 1 minute after the \"issued at\" (`iat`) time and formatted as a `NumericDate`.",
    "2-1": "Issued at, the time at which the JWT was issued. The value is formatted as a `NumericDate`.",
    "3-1": "Issuer of the token. Always set to “percolate.com”.",
    "4-1": "JWT ID, the case sensitive unique identifier of the token.",
    "5-0": "`sub`",
    "5-1": "Subject, the user accessing the UI extension that caused the JWT to be sent."
  },
  "cols": 2,
  "rows": 6
}
[/block]

**Non-standard Properties**

Also known as [custom claims.](https://auth0.com/docs/tokens/concepts/jwt-claims#custom-claims)

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`app_id`",
    "0-1": "The Percolate app id.",
    "1-0": "`location`",
    "1-1": "The id of the current UI location. One of the 7 location ids:\n`top_nav`\n`campaign_tab`\n`post_tab`\n`asset_tab`\n`request_tab`\n`task_tab`\n`settings`",
    "2-0": "`object_id`",
    "2-1": "The Percolate id of the resource being viewed based on the location.\n\nUndefined for `top_nav` and `settings` locations.",
    "3-0": "`object_scope_id`",
    "3-1": "The owner scope of the resource being viewed based on the location.\n\nUndefined for `top_nav` and `settings` locations.",
    "4-0": "`user_id`",
    "4-1": "The id of the current session user.",
    "5-0": "`tenant_id`",
    "5-1": "The id of the tenant/account the user is currently logged into."
  },
  "cols": 2,
  "rows": 6
}
[/block]

\#Defining lifecycle callbacks

Developers can optionally define callbacks for events happening throughout the life of an installation. When a lifecycle event is fired, a POST request will be made to the appropriate URL registered for the event.

Six events can be registered to a callback under the manifest’s `lifecycle` field. Each URL must use HTTPS to ensure data is sent securely.

Apps can verify that the request came from Percolate using the `X-Perc-App-Secret` request header, which is sent with all lifecycle requests. Its value should be the same as the app secret that encodes the UI extension’s JWTs.

[block:code]
{
  "codes": [
    {
      "code": "\"lifecycle\": {\n  \"install\": \"https://prclt-crostini.herokuapp.com/install\",\n  \"uninstall\": \"https://prclt-crostini.herokuapp.com/uninstall\",\n  \"enable\": \"https://prclt-crostini.herokuapp.com/enable\",\n  \"disable\": \"https://prclt-crostini.herokuapp.com/disable\",\n  \"update\": \"https://prclt-crostini.herokuapp.com/update\",\n  \"upgrade\": \"https://prclt-crostini.herokuapp.com/upgrade\"\n}",
      "language": "json",
      "name": "Example \"lifecycle\" manifest field"
    }
  ]
}
[/block]

\##Install

`install` is required for apps needing access to Percolate’s data as it provides the `access_token` needed to authorize requests.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"app_installation_id\": \"app_installation:12345\",\n  \"metadata_source\": \"notset\",\n  \"tenant_id\": \"tenant:123\",\n  \"access_token\": \"1QJiVA_PM_I_t3bqr0xEWsvAIksjg...q8YY8yw_7o7OS88\",\n  \"machine_user_id\": \"user:123\",\n  \"manifest_version\": \"1.0.0\",\n  \"access_scopes\": [\n    {\n      \"scope_id\": \"license:100\",\n      \"is_hierarchical\": true\n    }\t\n  ]\n}",
      "language": "json",
      "name": "Example payload for \"install\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app is installed under.",
    "1-0": "`access_token`",
    "1-1": "An `OAuth 2.0` compliant Bearer token. [See here](https://percolate.dev/docs/authorization-authentication-quickstart#section-testing-out-an-access-token) for a usage example. It does not expire.",
    "2-0": "`machine_user_id`",
    "2-1": "The bot user id used by the app to access data from Percolate.",
    "3-0": "`manifest_version`",
    "3-1": "The semantic version of the installed app as defined in the manifest.",
    "4-0": "`access_scopes`",
    "4-1": "The list of `access_scope` JSON objects defining which scopes to authorize data access.\n\n`scope_id` - the scope id within a tenant where data access is granted.\n\n`is_hierarchical` - if `true`,  child scopes under the above `scope_id` will be granted data access.",
    "5-0": "`app_installation_id`",
    "6-0": "`metadata_source`",
    "6-1": "Enum that is initially set to `notset` - can also be `seismic` and `percolate`. Determines if the installation will have Seismic as the metadata source or Percolate i.e. metadata field data will come from Seismic or Percolate for mappings created in the future.",
    "5-1": "The `app_installation_id` for the given installation. Currently used for retrieving uix_visibility criteria."
  },
  "cols": 2,
  "rows": 7
}
[/block]

\##Enable

This callback is triggered when an installed app is enabled / activated. This means that Percolate will display the UI extensions as defined in the manifest under the appropriate conditions.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"tenant_id\": \"tenant:123\"\n}",
      "language": "json",
      "name": "Example payload for \"enable\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app is installed under."
  },
  "cols": 2,
  "rows": 1
}
[/block]

\##Disable

This callback is triggered when an installed app is disabled / made inactive. This means that the UI extensions that the app defines will become hidden.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"tenant_id\": \"tenant:123\"\n}",
      "language": "json",
      "name": "Example payload for \"disable\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app is installed under."
  },
  "cols": 2,
  "rows": 1
}
[/block]

\##Update

This callback is triggered when the data access scopes of an installed app have changed.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"tenant_id\": \"tenant:123\",\n  \"access_scopes\": [\n    {\n      \"scope_id\": \"license:100\",\n      \"is_hierarchical\": true\n    },\n    {\n      \"scope_id\": \"license:200\",\n      \"is_hierarchical\": false\n    }\t\n  ]\n}",
      "language": "json",
      "name": "Example payload for \"update\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app is installed under.",
    "1-0": "`access_scopes`",
    "1-1": "The list of `access_scope` JSON objects defining which scopes to authorize data access.\n\n`scope_id` - the scope id within a tenant where data access is granted.\n\n`is_hierarchical` - if true,  scopes nested under the above scope_id will be granted data access.",
    "h-0": "Property",
    "h-1": "Description"
  },
  "cols": 2,
  "rows": 2
}
[/block]

\##Upgrade

This callback is triggered when an installed app is upgraded.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"tenant_id\": \"tenant:123\",\n  \"manifest_version\": \"2.0.0\",\n}",
      "language": "json",
      "name": "Example payload for \"upgrade\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app is installed under.",
    "1-0": "`manifest_version`",
    "1-1": "The new version of the manifest the installation is now using."
  },
  "cols": 2,
  "rows": 2
}
[/block]

\##Uninstall

This callback is triggered when an installed app is uninstalled. At that time, the `access_token` provided by the install callback becomes permanently invalid. A new one will be reissued once the app is reinstalled.

[block:code]
{
  "codes": [
    {
      "code": "{\n  \"tenant_id\": \"tenant:123\"\n}",
      "language": "json",
      "name": "Example payload for \"uninstall\" callback"
    }
  ]
}
[/block]

[block:parameters]
{
  "data": {
    "h-0": "Property",
    "h-1": "Description",
    "0-0": "`tenant_id`",
    "0-1": "The id of the tenant/account the app was installed under."
  },
  "cols": 2,
  "rows": 1
}
[/block]

\#Registering an App

A a new custom app must be registered in order to be made available for installation. To register an app:

> 1. Go to **Settings > Developer > App** registration and then click the **+ New app** button in the upper right corner.

> 2. Upload your app's manifest JSON document.

[block:image]
{
  "images": [
    {
      "image": [
        "https://files.readme.io/6dd49b8-register_app.jpg",
        "register_app.jpg",
        1279,
        704,
        "#b4b3be"
      ]
    }
  ]
}
[/block]

> 3. The new app will now display on the **App registration** page.

The following actions can be taken on the app:

> **Upload new version**: Uploading a new manifest will update the definition of an existing app. A new version of the app is created each time a new manifest is uploaded.

[block:callout]
{
  "type": "info",
  "body": "App installations pointing to an old version will need to be upgraded by a system admin to reflect changes. This means that developers must support installations which use an older version of the app manifest until all tenants have upgraded past it."
}
[/block]

> **Delete**: Permanently removes the app and all its versions. **This action will fail if the app is currently installed in any tenant.**

> **Show secret**: Reveals the app secret in a pop-up modal. Apps with UI extensions must use this secret to decode the JWT sent to their UI extension URLs. It is also sent as a header `X-Perc-App-Secret` in lifecycle callbacks so that the app can authenticate that the callback came from Percolate.

Once registered, you can begin testing your new app by [installing it.](https://percolate.dev/docs/installing-and-managing-pdp-apps)