From 0166289a66b24671666763d20978d00cad033fbe Mon Sep 17 00:00:00 2001 From: Viljami Kuosmanen Date: Tue, 1 Sep 2026 17:54:58 +0300 Subject: [PATCH] docs: Key/Value Maps for the Integration Toolkit Adds a dedicated page for the new Key/Value Maps feature (JSON environment variables + $mapValue/$mapKey JSONata helpers) and cross-links it from the inbound mapping reference, the integration configuration table, the webhooks environment page and the environment variable types table. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GyxZKctokeU2ZVGv4L6pXN --- docs/environments/environments-secrets.md | 1 + .../integration-toolkit/configuration.md | 1 + .../integration-toolkit/inbound/mapping.md | 17 +++ .../integration-toolkit/key-value-maps.md | 103 ++++++++++++++++++ .../webhooks/environments-secrets.md | 16 +++ 5 files changed, 138 insertions(+) create mode 100644 docs/integrations/integration-toolkit/key-value-maps.md diff --git a/docs/environments/environments-secrets.md b/docs/environments/environments-secrets.md index b2780dd7..90dd51ab 100644 --- a/docs/environments/environments-secrets.md +++ b/docs/environments/environments-secrets.md @@ -28,6 +28,7 @@ Environment variables solve all three. Store your credentials and URLs as variab |------|-------------|-----------| | **String** | Plain text value. Use for URLs, IDs, feature flags, and other non-sensitive config. | Yes -- value is visible in the UI and returned by the API. | | **SecretString** | Encrypted value. Use for API keys, OAuth secrets, passwords, and tokens. | No -- value is write-only. Once saved, it cannot be read back through the UI or API. | +| **JSON** | A JSON object (up to 32 KB). Used for [Key/Value Maps](/docs/integrations/integration-toolkit/key-value-maps) — lookup tables read in JSONata via `$env.` with `$mapValue` / `$mapKey`. Not available in `{{ env.* }}` templates. | Yes -- value is visible in the UI and returned by the API. | ## Naming your variables diff --git a/docs/integrations/integration-toolkit/configuration.md b/docs/integrations/integration-toolkit/configuration.md index 500764b2..2281a22a 100644 --- a/docs/integrations/integration-toolkit/configuration.md +++ b/docs/integrations/integration-toolkit/configuration.md @@ -63,6 +63,7 @@ curl -X POST 'https://erp-integration.sls.epilot.io/v2/integrations' \ | `integration_type` | string | No | `erp` (default) or `connector` | | `connector_config` | object | No | Shared config for connector-type integrations (base URL, auth) | | `protected` | boolean | No | When `true`, prevents deletion and restricts modifications to admin users | +| `maps` | array | No | [Key/Value Maps](./key-value-maps.md) declared on this integration (`key`, `label`, `description`, `value`). Saving the integration writes each map to the organization's environment as a `JSON` variable. | ### Listing Integrations diff --git a/docs/integrations/integration-toolkit/inbound/mapping.md b/docs/integrations/integration-toolkit/inbound/mapping.md index bc1942fc..305050eb 100644 --- a/docs/integrations/integration-toolkit/inbound/mapping.md +++ b/docs/integrations/integration-toolkit/inbound/mapping.md @@ -314,6 +314,22 @@ All three are visible via the standard ERP monitoring stream alongside extractio - Resolved values are cached for **60 seconds per `(org, key)`** by the `@epilot/environments` package. - Adding or removing an env var becomes visible to `env_var_ref` within 5 minutes; changing the value of an existing non-secret var becomes visible within 60 seconds. +### Key/Value Maps in JSONata {#kv-maps} + +For enum-like translations (salutation codes, status codes, country codes) use a [Key/Value Map](../key-value-maps.md) instead of hand-written `? :` chains. Maps are `JSON` environment variables declared on the integration and are available inside every `jsonataExpression` as `$env.`, together with two helpers: + +```json +{ + "attribute": "salutation", + "jsonataExpression": "$mapKey($env.salutation, $string(Person1Anredekennzeichen))" +} +``` + +- `$mapValue(map, key, default?)` — forward lookup (epilot value → external code) +- `$mapKey(map, value, default?)` — reverse lookup, first matching key wins (external code → epilot value) + +`$env` only contains non-secret variables. If the referenced map does not exist the expression fails with `first argument must be an object`, which is reported through the standard mapping monitoring. See [Key/Value Maps](../key-value-maps.md) for the recommended table shape and the full rule set. + ## Repeatable Fields Email and phone fields in epilot are stored as arrays. Use `_type` to specify the field type: @@ -794,3 +810,4 @@ Transform and validate in one expression: - [Relations](./relations.md) - Link entities together - [Pricing](./pricing.md) - Map ERP line items and calculate prices - [Meter Readings](./meter-readings.md) - Handle meter reading data +- [Key/Value Maps](../key-value-maps.md) - Re-usable lookup tables for enum-like values diff --git a/docs/integrations/integration-toolkit/key-value-maps.md b/docs/integrations/integration-toolkit/key-value-maps.md new file mode 100644 index 00000000..752676ff --- /dev/null +++ b/docs/integrations/integration-toolkit/key-value-maps.md @@ -0,0 +1,103 @@ +--- +sidebar_position: 7 +title: Key/Value Maps +description: Re-usable lookup tables for translating enum-like values between epilot and external systems +--- + +# Key/Value Maps + +Key/Value Maps are re-usable lookup tables for enum-like values that differ between epilot and an external system — salutations, country codes, contract status codes, payment methods, tariff identifiers. + +A typical example: epilot stores a contact's salutation as `Mr.` or `Ms. / Mrs.`, while the ERP expects `1` or `2`. Instead of repeating the same `? :` chain in every mapping, declare the table once and look it up with `$mapValue` / `$mapKey` in any JSONata expression. + +```json title="Map: salutation" +{ + "Mr.": "1", + "Mr": "1", + "Ms. / Mrs.": "2", + "Company": "4" +} +``` + +## How maps are stored + +A map is an [environment variable](/docs/environments/environments-secrets) of type **`JSON`** — a flat JSON object whose keys and values are strings (up to 32 KB). Maps are: + +- **Declared on an integration** in the Integration Hub under the **Maps** tab (name, environment key, description, entries). The declaration travels with the integration, so the map is re-created when the integration is set up in another organization. +- **Stored globally** as an environment variable. Saving the integration writes the map to the environment; the environment value is what every lookup uses at runtime. If two integrations declare the same key, the last one saved wins. +- **Editable in both places** — the Integration Hub shows the current environment value, and the variable also appears in [Environments & Secrets](/docs/environments/environments-secrets) with a JSON editor. + +Because maps are ordinary environment variables, they are never secret, and they follow the same [naming rules](/docs/environments/environments-secrets#naming-your-variables) (`salutation`, `erp.salutation`, `lima.contract_status`, …). + +## Using maps in JSONata + +Wherever the Integration Toolkit or Webhooks evaluate JSONata, the organization's non-secret environment variables are available as `$env`, and two helper functions operate on map objects: + +| Function | Direction | Returns | +|----------|-----------|---------| +| `$mapValue(map, key, default?)` | key → value | The value stored under `key`, or `default` when the key is missing | +| `$mapKey(map, value, default?)` | value → key | The **first** key whose value equals `value`, or `default` when nothing matches | + +```jsonata title="Outbound: epilot value → ERP code" +$mapValue($env.salutation, salutation, "!") +``` + +```jsonata title="Inbound: ERP code → epilot value" +$mapKey($env.salutation, Person1Anredekennzeichen) +``` + +Rules worth knowing: + +- Lookup keys are compared as strings — `$mapValue($env.salutation, 1)` and `$mapValue($env.salutation, "1")` are the same lookup. +- `$mapKey` compares values with strict equality: a map value `"1"` does not match the number `1`. Coerce with `$string()` when the source field is numeric. +- When no `default` is given and nothing matches, the result is `undefined` and the mapped attribute is simply omitted — the same behaviour as any other undefined JSONata result. +- If the first argument is not an object (for example the environment variable does not exist yet), the expression fails with `$mapValue: first argument must be an object` / `$mapKey: …`. In inbound use cases this surfaces as a mapping error in monitoring; in webhooks the delivery fails. +- Values are read through the environments cache, so a change to a map becomes visible to running integrations within about 60 seconds. + +### Where `$env`, `$mapValue` and `$mapKey` are available + +- Inbound use cases — every `jsonataExpression` field mapping and entity-level `jsonata` expression, including the mapping simulation endpoint. +- Outbound webhooks — the payload transformation and multipart form-field expressions. +- Outbound file proxy — request body templates and delivery expressions. + +## Recommended shape: one map, both directions + +Model each map as **epilot value → external value**. The forward lookup (`$mapValue`) then serves outbound integrations and the reverse lookup (`$mapKey`) serves inbound ones, so one table covers both directions. + +- **Many-to-one** is expressed with extra keys pointing at the same value (`"Mr": "1"`, `"Mr.": "1"`). On the reverse lookup the *first* matching key wins, so list the canonical epilot value first. +- **Keep external codes in the values, not the keys.** JavaScript orders integer-like object keys (`"0"`, `"1"`, `"10"`) numerically ahead of other keys, which would make "first match" in `$mapKey` depend on the number, not on your ordering. Epilot values such as `"Mr."` keep the order you typed. +- **Defaults belong at the call site**: `$mapValue($env.salutation, salutation, "!")` — the map stays a pure table and different mappings can choose different fallbacks. +- **No implicit normalisation.** Lookups are exact; if the source system sends `herr` or `Herr ` inconsistently, normalise in the expression first: `$mapKey($env.salutation, $trim($lowercase(Anrede)))` against a map whose values are lower-cased. + +## Full example + +Map `salutation` (declared on the ERP integration): + +```json +{ "Mr.": "1", "Mr": "1", "Ms. / Mrs.": "2", "Company": "4" } +``` + +Inbound field mapping — ERP customer record to contact: + +```json title="Inbound use case" +{ + "attribute": "salutation", + "jsonataExpression": "Adressart = 'FIRMA' ? 'Company' : $mapKey($env.salutation, $string(Person1Anredekennzeichen))" +} +``` + +Outbound webhook payload transformation — contact to ERP request: + +```jsonata title="Webhook payload transformation" +{ + "kundennummer": entity.customer_number, + "anredekennzeichen": $mapValue($env.salutation, entity.salutation, "!"), + "nachname": entity.last_name +} +``` + +## Next Steps + +- [Mapping](./inbound/mapping.md) — all inbound field mapping types, including `env_var_ref` for scalar environment values +- [Environments & Secrets](/docs/environments/environments-secrets) — variable types, naming, and API +- [Environments & Secrets in Webhooks](/docs/integrations/webhooks/environments-secrets) — `{{ env.* }}` and `$env` in webhooks diff --git a/docs/integrations/webhooks/environments-secrets.md b/docs/integrations/webhooks/environments-secrets.md index 7bcf1763..e48e0797 100644 --- a/docs/integrations/webhooks/environments-secrets.md +++ b/docs/integrations/webhooks/environments-secrets.md @@ -37,6 +37,22 @@ You can use environment variable references in: When the webhook fires, all `{{ env.* }}` references are resolved server-side to the organization's actual values before the HTTP request is sent. SecretString values are decrypted only at this point and never logged. +## JSON maps in payload transformations + +Environment variables are also available inside the JSONata **payload transformation** (and multipart form-field expressions) as `$env.`. This is most useful with variables of type `JSON`, which hold [Key/Value Maps](/docs/integrations/integration-toolkit/key-value-maps) — lookup tables for translating epilot values into the codes your endpoint expects: + +```jsonata title="Payload transformation" +{ + "kundennummer": entity.customer_number, + "anredekennzeichen": $mapValue($env.salutation, entity.salutation, "!") +} +``` + +- `$mapValue(map, key, default?)` — forward lookup (epilot value → external code) +- `$mapKey(map, value, default?)` — reverse lookup, first matching key wins + +`$env` exposes non-secret variables only; SecretString values are never available to JSONata. If a referenced map does not exist, the transformation fails and the delivery is reported as failed, like any other JSONata error. + ## Autocomplete The webhook configuration UI provides autocomplete when you type `{{ env.`. It suggests matching variable keys from your organization and auto-completes the closing braces.