> For the complete documentation index, see [llms.txt](https://docs.pandium.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata.md).

# 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. To move an existing integration onto 1.0, see [Migrating to Manifest Version 1.0](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/migrating-to-manifest-version-1.0.md).
{% 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.

Metadata can also hold optional information the customer needs while authenticating or configuring a connection, such as an account identifier discovered during authentication, or the option lists behind [dynamic configurations](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dynamic-configurations.md).

### How does Tenant Metadata Work?

Metadata belongs to a tenant, and its shape is declared by the `metadata_schema` of the integration release that tenant is on. A run's lifecycle around it is:

1. **Before the run**, Pandium writes the tenant's current metadata (or, for a [rerun](#reruns), the metadata its rerun type selects) to a JSON file and puts the path in `PAN_CTX_TENANT_METADATA_FILE`.
2. **During the run**, your integration reads that file, and prints the values it wants to persist as a single JSON object on stdout.
3. **After the run**, Pandium validates that stdout against the release's `metadata_schema`. On success the document is merged into the tenant's metadata. On failure the run is marked **Failed (Metadata Validation)** and the tenant's metadata is left untouched.

Metadata can also be read and written [through the API](#updating-and-reviewing-tenant-metadata-using-the-pandium-api) without waiting for a run.

### Adding Metadata to your Integration Release

Tenant metadata requires manifest `version: 1.0` or above. 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 %}

#### Schema rules Pandium enforces at build time

A release is only created if its `metadata_schema` satisfies all three of the following. Any failure fails the build with a message naming the offending property, so a broken schema never reaches a tenant.

* **Undeclared keys are rejected unless you say otherwise.** If your `schema` does not declare an `additionalProperties` value, Pandium sets `additionalProperties: false` on it. A run whose stdout introduces a key you didn't declare then fails validation. Write `additionalProperties: true` yourself if you want a permissive schema; an explicit value, `true` or `false`, is never overwritten.
* **Every key listed under `required` must declare a `default`.** Pandium gathers the defaults declared across the schema and validates that document against the schema itself. A required key with no default, or a default that doesn't satisfy its own property's type and constraints, fails the build. This guarantees a tenant's metadata is complete and valid from the moment the tenant is created, before any run has happened.
* **`pandium` is a reserved top-level property name** (in any casing). Pandium adds that key to your schema for its own use, so declaring one yourself is rejected.

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

The whole of a run's stdout is validated against the release's `metadata_schema`, so every key your integration prints must be declared in that schema. An undeclared key fails validation, because `additionalProperties` defaults to `false` on manifest version 1.0. Set `additionalProperties: true` yourself if you want a permissive schema.

A document that passes validation is **merged** into the tenant's existing metadata rather than replacing it, key by key, following [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396): nested objects merge, and printing an explicit `null` for a key deletes it. So a run that only wants to advance a sync cursor can print just that one key.

A document that fails validation leaves the tenant's metadata untouched, and the run ends in **Failed (Metadata Validation)**.

#### Reruns

The metadata a rerun receives depends on the rerun type:

* **Original configuration**: the metadata the original run received, restored from that run's snapshot. If the original run has no snapshot, the tenant's current metadata is used instead.
* **Current configuration**: the tenant's current metadata, not a snapshot taken when the original run started.
* **Custom configuration**: a JSON object you supply, which **replaces** the tenant's metadata for that run rather than merging into it; any key you leave out is absent from the run. It is validated against `metadata_schema` before the rerun starts, and invalid JSON or a document that fails the schema is rejected with a `422` without creating a run.

In every case the rerun's own stdout is validated and merged as described above, unless you opt out of saving the result. A custom rerun that saves its result also writes the supplied document to the tenant's metadata before the run starts.

### 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.md) and [dependent selectors](/getting-started/anatomy-of-an-integration/pandium.yaml-spec/dependent-selector-configurations.md). 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 and Reviewing Tenant Metadata Using the Pandium API

Metadata can be read and written directly, without waiting for a run. Every write is validated against the release's `metadata_schema`, and a document that fails validation is rejected with a `422`.

<table><thead><tr><th width="90">Method</th><th width="290">Path</th><th>Behavior</th></tr></thead><tbody><tr><td>GET</td><td><code>/v2/tenants/{tenant_id}/metadata</code></td><td>Returns the tenant's metadata with the release's schema defaults filled in for any key the tenant doesn't have.</td></tr><tr><td>PATCH</td><td><code>/v2/tenants/{tenant_id}/metadata</code></td><td>Merges the given document into the tenant's metadata key by key. An explicit <code>null</code> deletes a key.</td></tr><tr><td>PUT</td><td><code>/v2/tenants/{tenant_id}/metadata</code></td><td>Replaces the tenant's metadata wholesale. Nothing is merged and no defaults are filled in, so omitting a key deletes it.</td></tr><tr><td>POST</td><td><code>/v2/tenants/{tenant_id}/metadata/reset</code></td><td>Resets the tenant's metadata to the schema defaults of its current integration release.</td></tr></tbody></table>

Full request and response details are in the [API reference](https://docs.pandium.com/reference/pandium-api). Which fields a tenant accepts depends on the release it is on; `GET /v2/tenants/{tenant_id}/release` returns that release's schema.

### Reviewing Tenant Metadata in the Pandium Integration Hub

If your tenant is on a release that is using tenant metadata, you can review and change that tenant's current metadata from the tenant's page:

1. Pull up the tenant in question
2. In the header, next to **Tenant Metadata**, select **Show** to view the current metadata, **Edit** to replace it (the same full replace as `PUT`), or **Reset** to restore the release's schema defaults

   <figure><img src="https://4017407078-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MfJn-9R_dn6dvcGNcdk%2Fuploads%2Fgit-blob-4a2645985990e776ed1094820bf40874a8ef248b%2Ftenant-metadata-hub-actions.png?alt=media" alt="Tenant header showing the Tenant Metadata row with Show, Edit and Reset actions"><figcaption></figcaption></figure>

Edit and Reset are unavailable while the tenant has a run in progress.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pandium.com/getting-started/anatomy-of-an-integration/pandium.yaml-spec/tenant-metadata.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
