Entity Relations
Relations connect entities together in epilot. The ERP Toolkit supports creating and updating 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 for relations:
- All relations in a mapping are resolved 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​
- Meter Readings - Handle meter reading data
- Examples - Complete integration examples