Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/environments/environments-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<key>` with `$mapValue` / `$mapKey`. Not available in `{{ env.* }}` templates. | Yes -- value is visible in the UI and returned by the API. |

## Naming your variables

Expand Down
1 change: 1 addition & 0 deletions docs/integrations/integration-toolkit/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions docs/integrations/integration-toolkit/inbound/mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<key>`, 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:
Expand Down Expand Up @@ -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
103 changes: 103 additions & 0 deletions docs/integrations/integration-toolkit/key-value-maps.md
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions docs/integrations/webhooks/environments-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<key>`. 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.
Expand Down
Loading