Entity Relations
Relations connect entities in epilot. The ERP Toolkit creates and updates relations between entities during synchronization.
Relation Basicsโ
A relation links one entity to another. For example, linking a contract to its customer:
{
"entity_schema": "contract",
"unique_ids": ["contract_number"],
"fields": [
{ "attribute": "contract_number", "field": "contractId" },
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId"
}
}
]
}
Relation Configurationโ
Required Propertiesโ
| Property | Type | Description |
|---|---|---|
entity_schema | string | The schema of the related entity |
unique_ids | string[] | Fields to identify the related entity |
source_field | string | Field in the payload containing the identifier value |
Optional Propertiesโ
| Property | Type | Default | Description |
|---|---|---|---|
operation | string | _set | How to handle existing relations |
enabled | string | - | Condition to process this relation |
Relation Operationsโ
Set (_set)โ
Replaces all existing relations with the new one:
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId",
"operation": "_set"
}
}
Append (_append)โ
Adds new unique relations to existing ones with automatic deduplication:
{
"attribute": "related_contracts",
"relation": {
"entity_schema": "contract",
"unique_ids": ["contract_number"],
"source_field": "relatedContractId",
"operation": "_append"
}
}
If a relation with the same entity_id already exists, it will not be added again.
Append All (_append_all)โ
Adds all relations without deduplication (allows duplicates):
{
"attribute": "audit_contacts",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "auditContactId",
"operation": "_append_all"
}
}
Use _append_all when you explicitly need to allow duplicate relations.
Nested Identifier Accessโ
Access nested data for the relation identifier:
{
"attribute": "billing_account",
"relation": {
"entity_schema": "account",
"unique_ids": ["account_number"],
"source_field": "$.billing.accountId"
}
}
Multiple Relationsโ
Array of Relationsโ
Link to multiple entities from an array in the payload:
{
"attribute": "meters",
"relation": {
"entity_schema": "meter",
"unique_ids": ["meter_number"],
"source_field": "meterIds"
}
}
Given this input:
{
"meterIds": ["M001", "M002", "M003"]
}
Creates relations to all three meters.
Multiple Relation Typesโ
Link to different entity types:
{
"entity_schema": "contract",
"unique_ids": ["contract_number"],
"fields": [
{ "attribute": "contract_number", "field": "contractId" },
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId"
}
},
{
"attribute": "billing_account",
"relation": {
"entity_schema": "account",
"unique_ids": ["account_number"],
"source_field": "billingAccountId"
}
},
{
"attribute": "meters",
"relation": {
"entity_schema": "meter",
"unique_ids": ["meter_number"],
"source_field": "meterIds"
}
}
]
}
Conditional Relationsโ
Process relations only when conditions are met:
{
"attribute": "partner",
"relation": {
"entity_schema": "contact",
"unique_ids": ["partner_number"],
"source_field": "partnerId",
"enabled": "$exists(partnerId) and partnerId != ''"
}
}
Advanced Relations Configurationโ
Relations with Itemsโ
For more control, use the relations property with items array:
{
"attribute": "billing_account",
"relations": {
"operation": "_set",
"items": [
{
"entity_schema": "billing_account",
"_tags": ["PRIMARY"],
"unique_ids": [
{
"attribute": "external_id",
"field": "accountId"
}
]
}
]
}
}
Each item in unique_ids supports three value sources:
| Source | Example |
|---|---|
field | { "attribute": "external_id", "field": "accountId" } |
jsonataExpression | { "attribute": "full_name", "jsonataExpression": "firstName & ' ' & lastName" } |
constant | { "attribute": "source", "constant": "ERP" } |
Dynamic Relations with JSONataโ
Generate relation items dynamically from array data:
{
"attribute": "contacts",
"relations": {
"operation": "_set",
"jsonataExpression": "persons.{ \"entity_schema\": \"contact\", \"unique_ids\": [{ \"attribute\": \"email\", \"constant\": email }], \"_tags\": [role] }"
}
}
Input:
{
"persons": [
{ "email": "john@example.com", "role": "OWNER" },
{ "email": "jane@example.com", "role": "USER" }
]
}
This creates relations to both contacts with their respective role tags.
Relation Referencesโ
Relation references ($relation_ref) link to a specific item within a repeatable attribute on a related entity, such as a specific address:
{
"attribute": "billing_address",
"relation_refs": {
"operation": "_set",
"items": [
{
"entity_schema": "contact",
"unique_ids": [
{
"attribute": "customer_number",
"field": "CustomerNumber"
}
],
"path": "address",
"value": {
"attribute": "address",
"operation": "_append",
"jsonataExpression": "{ \"street\": BillingStreet, \"city\": BillingCity, \"country\": 'DE', \"postal_code\": BillingPostalCode }"
}
}
]
}
}
Processing flow:
- Find or create the contact by
customer_number - Append the address to the contact's address attribute
- Create a
$relation_reflinking to that specific address item
The system automatically preserves _id values when updating repeatable attributes to maintain stable references.
Relation Resolution Strategyโ
The ERP Toolkit uses an all-or-nothing strategy:
- All relations in a mapping resolve before any entity updates
- If any relation cannot be found, the entire update fails
- This ensures data consistency
Resolution Flowโ
Parse Relation Config
โ
โผ
Extract Identifier Value
โ
โผ
Search for Related Entity
โ
โโโโ Found โโโโโโโถ Store Entity ID
โ
โโโโ Not Found โโโถ Fail Entire Update
Creating Missing Related Entitiesโ
Use post_actions to create related entities if they don't exist:
{
"entity_schema": "contract",
"unique_ids": ["contract_number"],
"fields": [
{ "attribute": "contract_number", "field": "contractId" },
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId"
}
}
],
"post_actions": {
"create_missing_relations": true
}
}
With this configuration, if the related contact doesn't exist, it will be created with the identifier value.
Relation Attribute Typesโ
Relations in epilot are stored with metadata. You can specify the relation type:
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId",
"relation_type": "primary_contact"
}
}
Error Handlingโ
Related Entity Not Foundโ
{
"status": "error",
"message": "Related entity not found",
"error": {
"code": "RELATION_NOT_FOUND",
"details": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"identifier_value": "C001"
}
}
}
Resolution Options:
- Ensure the related entity exists before processing
- Enable
create_missing_relationsin post_actions - Make the relation conditional with
enabled
Multiple Matches for Relationโ
{
"status": "error",
"message": "Multiple entities found for relation",
"error": {
"code": "AMBIGUOUS_RELATION"
}
}
Resolution: Use more specific unique identifiers or clean up duplicate data.
Best Practicesโ
Order Your Entity Processingโ
Process parent entities before children:
{
"entities": [
{
"entity_schema": "contact", // Process first
"unique_ids": ["customer_number"],
"fields": [...]
},
{
"entity_schema": "contract", // Process second, can now reference contact
"unique_ids": ["contract_number"],
"fields": [
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId"
}
}
]
}
]
}
Use Conditional Relationsโ
Avoid failures from missing optional relations:
{
"attribute": "secondary_contact",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "secondaryCustomerId",
"enabled": "$exists(secondaryCustomerId)"
}
}
Validate Data Before Processingโ
Pre-validate relation data with JSONata:
{
"attribute": "customer",
"relation": {
"entity_schema": "contact",
"unique_ids": ["customer_number"],
"source_field": "customerId",
"enabled": "$type(customerId) = 'string' and $length(customerId) > 0"
}
}
Next Stepsโ
- Pricing - Map ERP line items and calculate prices
- Meter Readings - Handle meter reading data
- Examples - Complete integration examples