Integrate external product catalogs with epilot
## Configuration To add an **External Product Catalog** component, create a new App or update an existing App.
All development is performed using the built-in configuration editor accessible from the component editor.
The editor provides you with autocomplete and validation of the configuration. That way you can start with the example provided in this documentation and fine-tune your external product catalog without ever leaving the editor.
## Hooks
Hooks allow changing or adding functionality to external product catalogs.
They typically rely on your API for the execution of any necessary logic and expect a certain response.
### Supported Hooks / Catalog Types
The interface supports two types of data exchange, depending on the block being used in the journey:
- **Products**: Returns an array of products (`type: "products"`).
- **Product Recommendations**: Returns a source product and a list of offer products (`type: "product-recommendations"`).
### Template Variables
You can use template variables throughout your configuration to dynamically inject values from various sources. Template variables use the `}` syntax.
#### Available Variables
- **`Options.*`**: Access values from the app options configured during installation
- Example: `}`, `}`
- **`AuthResponse.*`**: Access data from the authentication response data
- Example: `}`
- Use this to extract tokens or other authentication data returned by your auth endpoint
- **`Context.*`**: Access properties from the current context (e.g. Journey Context)
- Example: `}`, `}`
### Example
Below is an example of a configuration for the `products` and `product-recommendations` hooks, assuming a typical OAuth2 authentication flow, where the client credentials are stored in the app options.
```json title="Products hook with OAuth2 authentication"
,
"auth": }/auth/token",
"method": "POST",
"headers": }",
"Content-Type": "application/x-www-form-urlencoded"
},
"body": }"
}
},
"call": }/products",
"method": "POST",
"headers": }"
}
}
},
,
"auth": }/auth/token",
"method": "POST",
"headers": }",
"Content-Type": "application/x-www-form-urlencoded"
},
"body": }"
}
},
"call": }/product-recommendations",
"method": "POST",
"headers": }"
}
}
}
]
}
```
### Security Considerations
> **Caution: Never hardcode sensitive credentials in your configuration. Always store API keys and secrets as app options, which are encrypted at rest.**
- **Credentials Storage**: Store sensitive credentials (like API keys) as app options rather than hardcoding them
- **Token Expiration**: Ensure your authentication tokens have appropriate expiration times
- **HTTPS**: Always use HTTPS URLs for authentication endpoints and redirects
- **Access Control**: Implement proper authorization checks on your authentication endpoint to ensure only authorized clients can obtain tokens
## Usage in Journeys
After your External Product Catalog component is configured and the app is installed, it can be used within epilot's Journeys. Currently we support seamless integration of the external product catalog into the journey via Product Blocks and Product Recommendations Blocks (beta).
When a journey creator adds a **Product Block** or **Product Recommendations Block** in a journey, they can select the integration as the source for products. Only hooks that are supported for the selected block type will be available (e.g. only `products` hook for Product Block, only `product-recommendations` hook for Product Recommendations Block).
This allows the journey to dynamically fetch products and pricing from your external catalog in journeys.
By default, only data from preceding steps (in their defined order) is passed to the steps/blocks data context. While this works for most use cases, you can configure the data context to also include data from subsequent steps, enabling non-linear journey flows. This configuration also makes the data each integration passes to the journey more explicit, lighter and easier to manage.
## Usage in Portals
After your External Product Catalog component is configured and the app is installed, it can also be used within epilot's Portals.
When configuring a Portal, you can add a **Product Block** and select the integration as the source for products. Only hooks that are supported for the selected block type will be available.
This allows the portal to dynamically fetch products and pricing from your external catalog.
## Integration Interface
To ensure seamless communication between epilot and your external catalog, your integration must respect the [External Catalog Integration Interface documentation](https://docs.api.epilot.io/pricing-api-external-catalog).
### Specification
The integration works as a request to your service endpoint with the following request and response:
1. **Request**: A payload containing the `context` of the journey, portal, or a custom one defined by you/epilot.
2. **Response**: A list of products or product recommendations in a specific format.
For detailed information on the request and response schemas, please refer to the [External Catalog Integration Interface documentation](https://docs.api.epilot.io/pricing-api-external-catalog).
### Response Examples
Below is an example of a response for the `product-recommendations` hook.
```json title="Product Recommendations Response Example"
],
"legal_footnotes": "Price valid for new customers. Prices may vary depending on consumption and region.",
"price": ,
"price_components": [
,
"name": "Working price",
"id": "price-component-1",
"is_composite_price": false,
"type": "recurring",
"billing_period": "monthly",
"tax": ,
"unit_amount_net_decimal": "0.35",
"unit_amount_gross_decimal": "0.4165",
"amount_subtotal_decimal": "35",
"amount_total_decimal": "41.65",
"currency": "EUR"
},
,
"unit_amount_net_decimal": "10",
"unit_amount_gross_decimal": "11.90",
"amount_subtotal_decimal": "10",
"amount_total_decimal": "11.90",
"currency": "EUR"
}
],
"total_details":
]
}
},
"billing_duration_amount": 24,
"billing_duration_unit": "months",
"notice_time_amount": 6,
"notice_time_unit": "weeks",
"termination_time_amount": 6,
"termination_time_unit": "weeks",
"renewal_duration_amount": 12,
"renewal_duration_unit": "months"
},
"metadata": {}
},
"offers": [
],
"legal_footnotes": "Price valid for new customers. Prices may vary depending on consumption and region.",
"price": ,
"price_components": [
,
"name": "Working price",
"id": "price-component-3",
"is_composite_price": false,
"type": "recurring",
"billing_period": "monthly",
"tax": ,
"unit_amount_net_decimal": "0.3697",
"unit_amount_gross_decimal": "0.44",
"amount_subtotal_decimal": "36.97",
"amount_total_decimal": "44.00",
"currency": "EUR"
},
,
"unit_amount_net_decimal": "10",
"unit_amount_gross_decimal": "11.90",
"amount_subtotal_decimal": "10",
"amount_total_decimal": "11.90",
"currency": "EUR"
}
],
"total_details":
]
}
},
"billing_duration_amount": 12,
"billing_duration_unit": "months",
"coupons": [
]
}
},
],
"legal_footnotes": "Price valid for new customers. Prices may vary depending on consumption and region.",
"price": ,
"price_components": [
,
"name": "Working price",
"id": "price-component-1",
"is_composite_price": false,
"type": "recurring",
"billing_period": "monthly",
"tax": ,
"unit_amount_net_decimal": "0.35",
"unit_amount_gross_decimal": "0.4165",
"amount_subtotal_decimal": "35",
"amount_total_decimal": "41.65",
"currency": "EUR"
},
,
"unit_amount_net_decimal": "10",
"unit_amount_gross_decimal": "11.90",
"amount_subtotal_decimal": "10",
"amount_total_decimal": "11.90",
"currency": "EUR"
}
],
"total_details":
]
}
},
"billing_duration_amount": 24,
"billing_duration_unit": "months",
"coupons": [
]
}
}
]
}
```
This allows portals and journeys to display dynamic product recommendations based on your external catalog, as shown below:
# What Are Components? ## The Foundation of epilot Apps Components are the fundamental building blocks that make up every app in the epilot ecosystem. Think of them as specialized modules that extend epilot's functionality in specific, targeted ways. Each component type is designed to integrate with a particular part of the platform, enabling seamless extensions that feel native to users. When you build an app for epilot, you're essentially creating one or more components that work together to deliver value. A single app might contain multiple components of different types, each addressing a specific use case or integration point. ## Why Components Matter This modular approach offers several key advantages: - **Targeted Integration**: Components connect precisely where they're needed in the epilot platform - **Flexible Combinations**: Mix different component types to create comprehensive solutions - **Focused Development**: Build only what you need without unnecessary complexity - **Consistent Experience**: Users benefit from a unified interface regardless of the app source ## Available Component Types epilot currently supports these component types: ### [Custom Journey Blocks](/docs/apps/components/custom-journey-block) These web components integrate directly into epilot's Journey Builder, allowing you to create specialized elements for data collection, visualization, or process automation. Journey Blocks appear in the Journey Builder palette and can be placed anywhere in a customer journey flow. ``` Use cases: Data collection forms, calculators, visualizations, third-party integrations ``` ### [Portal Extensions](/docs/apps/components/portal-extension) Portal Extensions enhance epilot's customer and installer portals with new sections, tools, or visualizations. They can be added to dashboards or specific sections to extend the portal's functionality with custom features. ``` Use cases: Custom dashboard widgets, specialized tools, data visualizations ``` ### [External Product Catalog](/docs/apps/components/external-product-catalog) External Product Catalog components integrate third-party product catalogs into epilot. They enable Product Blocks to fetch and display products and pricing from external systems, allowing customers to browse and select items from external catalogs directly within their journey experience. ``` Use cases: Custom product catalogs, journey products, journey product recommendations ``` ### [Custom Workflow Task](/docs/apps/components/custom-action) Custom Workflow Tasks extend epilot's automation engine with your own logic. You can either call an external endpoint (webhook-style) or run TypeScript code in a sandboxed environment directly within a workflow task. ``` Use cases: Third-party integrations, data syncing, custom processing steps in workflows ``` ### [API Proxy](/docs/apps/components/api-proxy) API Proxy components let your app call external APIs without exposing credentials to the browser. The proxy runs server-side, injecting authentication and signing requests so secrets never reach the client. ``` Use cases: Secure external API calls, OAuth integrations, credential-protected endpoints ``` ## On the Horizon The epilot platform continues to evolve, with new component types planned for future releases: ### Custom Journey Design Elements Extensions to the Journey Builder's design capabilities, including: - **Email Plugins**: For customizing and enhancing email templates and functionality ## Requesting New Component Types Have an idea for a new component type? We welcome feedback from the developer community. 1. **Evaluate Your Need**: Consider if existing component types could address your use case 2. **Define the Integration Point**: Identify where in epilot your component would integrate 3. **Describe the Value**: Outline the problems it would solve for epilot users 4. **Submit a Request**: [Contact us](mailto:support@epilot.cloud) with your proposal Our product team regularly reviews component requests and prioritizes them based on community interest and platform direction. ## App Surfaces & Communication When your component runs inside epilot, it's embedded in an iframe and communicates with the parent application through the `@epilot/app-bridge` library. Different component types are displayed on different "surfaces" within epilot. Learn more about [App Surfaces](/docs/apps/app-surfaces) to understand how your app communicates with epilot and receives authentication, entity context, and configuration data. ## Building Your First Component Ready to create your own component? The fastest way to get started is with the **epilot CLI**: ```bash npx epilot app init my-app npx epilot app add-component my-block --type CUSTOM_JOURNEY_BLOCK ``` The CLI manages the full app lifecycle — from scaffolding and validation to deployment and version management. Key commands: | Command | Description | | --- | --- | | `epilot app init` | Scaffold a new app project with `manifest.json` | | `epilot app add-component` | Add a component from a starter template | | `epilot app validate` | Validate your manifest before deploying | | `epilot app deploy` | Deploy your app to epilot | | `epilot app versions` | List all versions of your app | | `epilot app review` | Submit a version for public review | | `epilot app export` | Export an existing app as `manifest.json` | See the [CLI documentation](/docs/cli/commands/app) for the full command reference. Each component type also has a ready-to-use starter template available in the [app-templates](https://github.com/epilot-dev/app-templates) repository.
Enhance epilot portals with custom functionality and integrations
## Configuration All development is performed using the built-in configuration editor accessible from the component editor. The editor provides you with autocomplete and validation of the configuration. That way you can start with the example provided in this documentation and fine-tune your portal extension without ever leaving the editor.  ## Links The easiest way to extend the functionality of portals is to link to a third party system. This is possible using the "seamless link" concept. ### What are Seamless Links? Seamless links enable portal users to access third-party systems directly without manual authentication. When a user clicks a seamless link in the portal, epilot handles the authentication process in the background according to your configured rules, then redirects the user to the specified page in the third-party system with the necessary credentials or tokens. This provides a smooth user experience where portal users can navigate to external systems without interruption, while you maintain control over the authentication mechanism and security policies. #### How Seamless Links Work ```mermaid sequenceDiagram participant User as Portal User participant epilot as epilot Portal participant Auth as Your Auth Endpoint participant App as Third-Party System User->>epilot: Clicks seamless link epilot->>Auth: Authentication request (credentials + headers) Auth-->>epilot: Returns token/credentials epilot->>App: Redirect with token parameters App-->>User: Authenticated page displayed ``` The seamless link flow consists of two main steps: 1. **Authentication**: epilot makes a request to your authentication endpoint using the configured credentials and headers 2. **Redirect**: epilot uses the authentication response to construct a redirect URL with the necessary parameters (such as tokens) and sends the user to the target page All of this happens automatically in the background, so the portal user experiences a seamless transition to the third-party system. ### Configuration and Example Seamless links are configured in your app configuration. Each seamless link defines the authentication mechanism and the redirect behavior. The built-in component configuration editor will guide you through the available properties and their format. Here is an example hook configuration: ```json title="Seamless link configuration" , "description": , "auth": }/token", "method": "GET", "headers": }", "UID": "}" } }, "redirect": }/dashboard", "params": }", "contractId": "}" } } } ``` The configuration above adds a new option for quick actions like this:  ### Template Variables You can use template variables throughout your seamless link configuration to dynamically inject values from various sources. Template variables use the `}` syntax. #### Available Variables - **`Options.*`**: Access values from the app options configured during installation - Example: `}`, `}` - **`Contact.*`**: Access properties from the current portal user's contact entity - Example: `}`, `}` - **`AuthResponse.*`**: Access data from the authentication response - Example: `}`, `}` - Use this to extract tokens or other authentication data returned by your auth endpoint - **`Entity.*`**: Access properties from the current context entities - Example: `}`, `}` ### Example Use Cases 1. **Billing Dashboard**: Redirect portal users to a third-party billing system to check details of their last bill 2. **Document Management**: Provide direct access to a document repository 3. **Service Portal**: Integrate with external service management systems ### Security Considerations > **Caution: Never hardcode sensitive credentials in your configuration. Always store API keys and secrets as app options, which are encrypted at rest.** - **Credentials Storage**: Store sensitive credentials (like API keys) as app options rather than hardcoding them - **Token Expiration**: Ensure your authentication tokens have appropriate expiration times - **HTTPS**: Always use HTTPS URLs for authentication endpoints and redirects - **Access Control**: Implement proper authorization checks on your authentication endpoint to ensure only authorized portal users can obtain tokens ## Hooks Hooks allow changing or adding functionality to portals. They typically rely on your API for the execution of any necessary logic and expect a certain response. For certain functionalities, users can choose which hook is used in the portal settings drawer under Extensions.  There are currently three groups of hooks supported based on their use: - Time Series Data Retrieval - Data Existence Check/Retrieval - Data Validation ### Time Series Data Retrieval Hooks Data retrieval hooks enable portals to fetch and display time-series data from third-party systems. These hooks are used to power data visualizations and charts in the portal interface, allowing portal users to view real-time or historical data from integrated systems. #### Types of Data Retrieval Hooks Portals support three types of data retrieval hooks: 1. **Price Data Retrieval** (`priceDataRetrieval`): Fetches price information over time 2. **Consumption Data Retrieval** (`consumptionDataRetrieval`): Retrieves consumption or usage data 3. **Cost Data Retrieval** (`costDataRetrieval`): Fetches cost or billing data #### How Data Retrieval Hooks Work Data retrieval hooks follow a three-step process: 1. **Authentication**: epilot authenticates with your third-party system to obtain a token or credentials 2. **Data Retrieval**: Using the authentication token, epilot makes a request to your data API with the requested time range and interval 3. **Data Resolution**: The response is processed and extracted using the configured data path, then displayed in the portal The authentication step supports caching to reduce API calls and improve performance. The data retrieval supports configurable time intervals (e.g., hourly, daily) and automatically handles time range queries based on the portal user's selected view. #### Configuration and Example Data retrieval hooks are configured in your app configuration. Each hook defines the authentication mechanism, the data retrieval endpoint, and how to process the response. Below are examples for all three data retrieval hook types. All of these enable configuring users to add a Dynamic Tariff or Consumption block where your integration is pickable using the "Integration" dropdown.  ##### Price Data Retrieval ```json title="Price data retrieval hook" , "intervals": ["PT1H"], "auth": }/token", "method": "GET", "headers": }", "UID": "}" }, "cache": }-}", "ttl": "3600" } }, "call": }/price", "headers": }" }, "params": }", "to": "}", "interval": "}" } }, "resolved": } ``` ##### Consumption Data Retrieval ```json title="Consumption data retrieval hook" , "intervals": ["PT1H"], "auth": }/token", "method": "GET", "headers": }", "UID": "}" }, "cache": }-}", "ttl": "3600" } }, "call": }/consumption", "headers": }" }, "params": }", "to": "}", "interval": "}" } }, "resolved": } ``` ##### Cost Data Retrieval ```json title="Cost data retrieval hook" , "intervals": ["PT1H"], "auth": }/token", "method": "GET", "headers": }", "UID": "}" }, "cache": }-}", "ttl": "3600" } }, "call": }/cost", "headers": }" }, "params": }", "to": "}", "interval": "}" } }, "resolved": } ``` #### Template Variables Data retrieval hooks support the same template variables as any other hook, plus additional scope variables: #### Standard Variables - **`Options.*`**: Access values from the app options configured during installation - **`Contact.*`**: Access properties from the current portal user's contact entity - **`AuthResponse.*`**: Access data from the authentication response - **`Entity.*`**: Access data from the context entities like `Contract`. #### Scope Variables (for `call.params`) These variables are automatically provided by epilot based on the portal user's selected time range and interval: - **`Scope.from`**: Start timestamp of the requested time range (ISO 8601 format) - **`Scope.to`**: End timestamp of the requested time range (ISO 8601 format) - **`Scope.interval`**: Selected time interval (ISO 8601 duration format, e.g., `"PT1H"` for hourly) #### Best Practices - **Caching**: Use authentication token caching to reduce API load and improve response times - **Interval Support**: Support multiple intervals if your API allows it to give portal users flexibility with different data views - **Error Handling**: Ensure your API returns appropriate error responses that epilot can handle gracefully - **Time Zone Handling**: Take care when handling Time Zones and DSTs #### Returning Typed Data The consumption hook may return more than one record per timestamp by adding a `type` field to each record. This lets a single chart break a value down into multiple series — for example a household that both draws from and feeds into the grid, or a meter billed on two tariffs. ```json title="Typed consumption response (prosumer)" , , , , , ] } ``` The `type` values are free-form, but they only render meaningfully when the portal knows how to label, color and combine them. That information comes from the **Visualization Metadata** hook described below — each `type` returned by the data hook should match a `type_options[].id` returned by the metadata hook. #### Visualization Metadata Hook The `visualizationMetadata` hook returns runtime metadata describing **how** a visualization should be rendered for the current portal context (which meter, contract, etc. the user is looking at). The portal invokes it _before_ the data hook, with the same context, so the shape of the chart can vary per meter or contract — different tariff models, available intervals, or history depth. A `visualizationMetadata` hook is looked up implicitly per extension (one per extension); a data-retrieval hook does not need to reference it explicitly. The metadata response has three optional fields: - **`type_options`**: the series advertised for this context. Each option's `id` matches the `type` field on the data hook records. Options also carry a localized `label`, an `aggregation_group`, a `statistical_method`, a `unit`, a Spark `color`, and a display `precision`. - **`intervals`**: the intervals supported for this context (`PT15M`, `PT1H`, `P1D`, `P1M`). Prefer this over the now-deprecated `intervals` field on the data-retrieval hooks, so the supported intervals can vary per meter/contract. - **`data_range`**: the earliest (`from`) and latest (`to`) timestamps for which data is available, used to bound the date picker. The `statistical_method` on each type both describes the aggregation already applied to that type's data and dictates the chart shape: - `sum` → **bar chart**. Same-`aggregation_group` types are stacked into one bar; different groups render side-by-side. - `min` / `average` / `max` → **line chart**. Same-`aggregation_group` types render as an area band; different groups render as separate lines. Because the method is per-type, a single visualization can mix bar-shaped and line-shaped series. ```json title="Visualization metadata hook" }/token", "method": "GET", "headers": }", "UID": "}" }, "cache": }-}", "ttl": "3600" } }, "call": }/example/visualization/metadata", "headers": }" }, "params": }" } }, "resolved": } ``` > **Note: `use_static_ips` is deprecated on all hook types — prefer `secure_proxy` (route requests through the ERP Integration secure proxy by setting `integration_id` and `use_case_slug`). The `resolved.dataPath` field has also been renamed to `resolved.data_path`; the old name still works but is deprecated.** #### Examples The epilot **example integration** service is a reference backend that powers Dynamic Tariff and Consumption blocks against synthetic-but-realistic German energy data. It exposes the data-retrieval endpoints (`/example/price`, `/example/consumption`, `/example/cost`) and a `/example/visualization/metadata` endpoint, and accepts a `setup` query parameter that selects a predefined deployment scenario. Using the same `setup` across the metadata and data hooks keeps the advertised `type_options` aligned with the records the data hooks return. The endpoints also accept `from`, `to` and `interval` (and, for consumption/cost, an optional `multiplier` for B2B scenarios). The two scenarios below show the prosumer and load-cycle setups end-to-end. ##### Prosumer (feed-in / feed-out) A household with a rooftop PV system both imports from and exports to the grid. The `prosumer` setup advertises two series — `feed-in` (surplus exported to the grid, peaks midday) and `feed-out` (drawn from the grid, mostly at night) — as separate groups so they render as distinct series.  ```json title="Visualization metadata response (setup=prosumer)" , "aggregation_group": "feed-in", "statistical_method": "sum", "unit": "kWh", "color": "green", "precision": 2 }, , "aggregation_group": "feed-out", "statistical_method": "sum", "unit": "kWh", "color": "blue", "precision": 2 } ], "intervals": ["PT15M", "PT1H", "P1D", "P1M"], "data_range": } ``` ```json title="Consumption hook (setup=prosumer)" , "auth": }/token", "method": "GET", "headers": }", "UID": "}" }, "cache": }-}", "ttl": "3600" } }, "call": }/example/consumption", "headers": }" }, "params": }", "to": "}", "interval": "}", "setup": "prosumer" } }, "resolved": } ``` The matching consumption response (hourly) returns a `feed-in` and a `feed-out` record per timestamp — surplus during the day, grid draw at night: ```json title="Consumption response (setup=prosumer, interval=PT1H)" , , , , , ] } ``` For a line-chart variant of the same data, the example integration also ships a `prosumer-line` setup (feed-in/feed-out advertised with `statistical_method: average`). ##### Load Cycle (min / average / max) An industrial / B2B site is billed on instantaneous power (kW) rather than energy. The `load-cycle` setup advertises three series — `min`, `average` and `max` — sharing a single `aggregation_group` but each carrying its own `statistical_method`. The portal renders this as a min–max area band with the average drawn as a line on top.  ```json title="Visualization metadata response (setup=load-cycle)" , "aggregation_group": "load", "statistical_method": "min", "unit": "kW", "color": "slate", "precision": 0 }, , "aggregation_group": "load", "statistical_method": "average", "unit": "kW", "color": "primary", "precision": 0 }, , "aggregation_group": "load", "statistical_method": "max", "unit": "kW", "color": "red", "precision": 0 } ], "intervals": ["PT15M", "PT1H", "P1D"], "data_range": } ``` ```json title="Consumption response (setup=load-cycle, interval=PT1H)" , , , , , ] } ``` The hook configuration is identical to the prosumer consumption hook above, only with `"setup": "load-cycle"` in `call.params`. ##### Other Example Setups The example integration ships further setups you can point the `setup` parameter at to exercise different chart shapes and discovery payloads: | `setup` | Series (`type_options`) | Rendered as | Notes | | --- | --- | --- | --- | | `default` | `default` (Consumption, kWh) | Bar | Single-tariff household, all intervals, last 2 years. | | `dual-tariff` | `ht` (High tariff), `nt` (Night tariff) | Stacked bar | HT 06:00–22:00, NT otherwise; both in the `consumption` group. | | `prosumer` | `feed-in`, `feed-out` | Bars (separate groups) | Net grid flows for a 5 kWp PV household. | | `prosumer-line` | `feed-in`, `feed-out` | Area / line | Prosumer data as `statistical_method: average`. | | `load-cycle` | `min`, `average`, `max` | Min–max band + average line | Industrial load in kW. | | `consumption-with-load` | `consumption` (kWh, bar), `average-load` (kW, line) | Mixed bar + line | Two `aggregation_group`s in one chart. | | `daily-only` | `default` | Bar | Only `P1D` + `P1M` (e.g. monthly meter readings). | | `partial-history` | `ht`, `nt` | Stacked bar | Last 6 months only (e.g. recently switched provider). | | `current-month` | `default` | Bar | Current month only (`PT1H` + `P1D`), e.g. start-of-contract demos. | ### Data Existence Check/Retrieval Sometimes it is desired to check against a third party system before allowing a user to register or self-assign business objects to their account. At the same time, it might be necessary to load business entities to epilot before allowing user to proceed in cases epilot does not have all data on non-portal users. #### Registration Hook Use the registration hook to validate identifiers before creating a portal user. If the registration is valid, you can pass back an epilot Contact UUID that is associated with the portal user. If no body is specified, all identifiers configured for the portal and provided by the user are passed grouped by the schema. ```json title="Registration hook" }" }, "result": "}" } } ``` #### Template Variables Registration hooks support the standard template variables plus the identifiers context: - **`Options.*`**: Access values from the app options configured during installation - **`Identifiers.*`**: Access properties provided by the user to identify the entity groupped by schema #### Self-Assignment Hook Use the self-assignment hook when portal users attach additional contracts to their account. The hook can also include localized explanations shown to the user. If no body is specified, all identifiers configured for the portal and provided by the user are passed grouped by the schema. ```json title="Self-assignment hook" }" }, "body": }", "portal_user_id": "}", "contract_number": "}" } } }, "assignment_mode": "contact_to_portal_user", "explanation": } ``` #### Template Variables Self-assignment hooks support the standard template variables plus the identifiers context: - **`Options.*`**: Access values from the app options configured during installation - **`Contact.*`**: Access properties from the current portal user's contact entity - **`PortalUser.*`**: Access properties from the current portal user - **`Identifiers.*`**: Access properties provided by the user to identify the entity groupped by schema - **`CallResponse.*`**: Access data from the call response (for example in `call.result`) ### Data Validation #### Meter Reading Plausibility Use the plausibility check hook to validate meter readings before they are submitted and return limits for validation feedback. ```json title="Meter reading plausibility check hook" }" }, "body": }", "register_number": "}", "timestamp": "}", "value": "}" } }, "resolved": }", "lower_limit": "}", "upper_limit": "}" } } ``` #### Template Variables Meter reading plausibility hooks support the standard template variables plus meter reading context: - **`Options.*`**: Access values from the app options configured during installation - **`Contact.*`**: Access properties from the current portal user's contact entity - **`Meter.*`**: Access properties of the meter - **`MeterCounter.*`**: Access properties of the meter register - **`Reading.*`**: Access properties of the submitted reading - **`CallResponse.*`**: Access data from the call response For questions about portal extensions, [contact our developer support team](https://developers.epilot.cloud/contact). --- # Permissions Learn how to configure permissions your App requires to function properly. When building an app for the epilot platform, you may need to request specific permissions to access certain features or data. This guide will help you understand how to configure these permissions effectively. ## What Are Permissions? Permissions in epilot control access to various features and data within the platform. When you create an app, you can specify which permissions your app requires to function correctly. This ensures that users have the necessary access rights to use your app without encountering permission-related issues. ## How to Configure Permissions For Your App To configure permissions for your app, you need to create a role in your own organization and specify which grants (permissions) this role should have. This role can then be assigned to users who will be using your app. ### Step 1: Create a Role 1. Go to the **Access Management** section of your epilot organization (under settings). 2. Navigate to the **Roles** tab. 3. Click on **New Role**. 4. Enter a name for the role (e.g., "App User Role"). 5. Click **Save** to create the role. > **Info: A dedicated **App Role** type is coming soon. For now, please select the **User Role** for the role type.** ### Step 2: Configure Permissions 1. After creating the role, you will see a list of available permissions (grants). 2. Go to your App configuration in the epilot platform. 3. In the **Permissions** section, select the role you created in Step 1. and save the changes. ## How To Use Permissions in Your App Once your App is installed, a role you specified is automatically created in the customer's organization. This role will have the permissions you configured in Step 2. Now every component has different options to access those permissions. For example the Custom Action component can access the permissions through the `x-epilot-token` header in the http request. This token contains the necessary information to authenticate and authorize the action being performed. Check the specific component documentation for details on how to access permissions in your app. ## Supported Permissions by Component Type | Component Type | Permissions Supported | |---------------------------|-----------------------| | Custom Journey Block | No | | Portal Extension | No | | Custom Action | Yes | | API Proxy | No | --- Learn how to run the App in development now to get changes reflected live. # What is Development Mode? > **Tip: Development mode lets you see changes in real-time without creating new versions. Enable it during active development to skip the upload-install-test cycle.** Development mode is a feature that allows you to run your app in a local development environment. This mode enables you to see changes in real-time without having to update the app every time you make a change. It is particularly useful for testing and debugging your app during the development process. ## How to Run in Development Mode Head over to the components section and you will find a button to run the app in development mode. It enables development mode for the ***latest private version*** of your App. Once enabled, this particular version is pinned and cannot be changed. You can make changes and immediately see the results without reinstalling your App each time. With development mode enabled, you also have the ability to override certain component configuration values.   ## Custom Journey Block Overrides With development mode enabled, you can override the component URL of your Custom Journey Block. This allows you to point the component to a local development server or any other URL where your component is hosted. This way, you can test changes in real-time without having to publish a new version of your app.  Now once you go into a journey to connect this custom block, you will see the component url is overridden with the one you provided in the development mode. This allows you to test your changes in the context of a journey without having to publish a new version of your app. --- # Deploying & Updating Functions ## Adding a function ```bash # workflow function (code behind a flow action component) npx @epilot/cli app add-function reserve-slot --type workflow npx @epilot/cli app add-component reserve-slot-action --type CUSTOM_FLOW_ACTION_FUNCTION # scheduled function (--schedule implies --type scheduled) npx @epilot/cli app add-function sync-things --schedule "rate(30 minutes)" ``` This scaffolds `functions/Explore the full capabilities of the epilot platform without risking production environments. Your sandbox is your personal laboratory for innovation.
Build, test, and refine your apps in an environment that mimics production but forgives mistakes. Iterate quickly without fear of breaking changes.
Submit your request through our Developer Portal
After approval, sign in with your provided credentials and explore your new development space
Navigate to the app creation page and start building
Thoroughly test your app in various scenarios and refine until perfect
When you're confident in your app, submit it for review to make it publicly available
Experiment freely without affecting other systems or users in the epilot ecosystem
Test your app thoroughly in a realistic environment that matches production
Access all the necessary tools and features to build sophisticated apps
Find and fix issues without concern for production impacts
---
# Tech Stack & Architecture
> **Info: _epilot_ is a multi-tenant SaaS platform for energy suppliers, municipal utilities, grid operators, and solution providers. It provides a 360° energy XRM (extended relationship management) to digitize sales, service, and grid processes end-to-end.**
[What is an energy XRM?](https://www.epilot.cloud/en/blog/what-is-an-energy-xrm)
## Overview
The epilot application consists of:
- [The epilot 360 portal](/docs/architecture/microfrontends) — single-spa micro-frontend SPA
- [Embeddable Journey Frontends](/docs/journeys/journey-builder) — customer-facing forms and flows
- [epilot Portals](/docs/portals/customer-portal) — white-label self-service portals for end-users, with [mobile app](/docs/portals/mobile-app) support
- Serverless microservices with [public APIs](/api)
## Architecture
epilot runs entirely on **AWS** in the EU (Frankfurt) region as a cloud-native, serverless platform.
The backend is composed of independent **microservices** deployed as Lambda functions behind API Gateway. Services communicate asynchronously through EventBridge and SQS. All APIs follow an **API-first** design — OpenAPI contracts are defined before implementation, powering auto-generated SDKs and documentation.
The frontend consists of multiple **independently deployable applications** — the epilot 360 portal (a single-spa micro-frontend SPA), embeddable journey frontends, and white-label end-user portals — all built with React or Svelte and sharing the Volt UI design system.
[](../../static/img/system-architecture.jpg)
## Tech Stack
### Frontend
| Technology | Role |
|---|---|
| [React](https://react.dev/) | Main UI framework |
| [Svelte](https://svelte.dev/) | Used in select micro-frontends |
| [single-spa](https://single-spa.js.org/) | Micro-frontend orchestration for the epilot portal |
| [Tailwind CSS](https://tailwindcss.com/) | Utility-first CSS framework |
| [Volt UI](https://www.npmjs.com/package/@epilot/volt-ui) | epilot design system (React and Svelte) |
| [Capacitor](https://capacitorjs.com/) | Hybrid native runtime for [portal mobile apps](/docs/portals/mobile-app) |
| [Axios](https://axios-http.com/) / [openapi-client-axios](https://www.npmjs.com/package/openapi-client-axios) | HTTP client with OpenAPI type generation |
### Backend
| Technology | Role |
|---|---|
| [AWS Lambda](https://aws.amazon.com/lambda/) | Serverless compute for all microservices |
| [API Gateway](https://aws.amazon.com/api-gateway/) (HTTP API) | API routing and authorization |
| [Step Functions](https://aws.amazon.com/step-functions/) | Workflow orchestration |
| [EventBridge](https://aws.amazon.com/eventbridge/) | Event-driven communication between services |
| [S3](https://aws.amazon.com/s3/) | File and object storage |
| [SQS](https://aws.amazon.com/sqs/) | Message queuing |
| [openapi-backend](https://www.npmjs.com/package/openapi-backend) | OpenAPI-first request handling |
| [Zod](https://zod.dev/) | Runtime schema validation |
### Databases
| Technology | Role |
|---|---|
| [DynamoDB](https://aws.amazon.com/dynamodb/) | General-purpose data store for microservices |
| [Elastic Cloud](https://www.elastic.co/cloud) | Full-text search and indexing for business entities |
| [ClickHouse Cloud](https://clickhouse.com/) | Analytical queries, [Datalake](/docs/datalake/epilot-datalake), workflow data, audit logs |
| [Momento](https://www.gomomento.com/) | Caching |
### Tech Radar
Thanks
", "is_unlicensed_org": false, "style": {}, "type": "Vendor", "symbol": "EPI", "pricing_tier": "professional", "free_user_limit": 50 } ```Thanks
", "is_unlicensed_org": false, "style": {}, "type": "Vendor", "symbol": "EPI", "pricing_tier": "professional", "free_user_limit": 50 } ```Thanks
", "is_unlicensed_org": false, "style": {}, "type": "Vendor", "symbol": "EPI", "pricing_tier": "professional", "free_user_limit": 50 }' ``` Using positional args for path parameters: ```bash epilot organization updateOrganization 739224 ``` Using stdin pipe: ```bash cat body.json | epilot organization updateOrganization -p org_id=739224 ``` With JSONata filter: ```bash epilot organization updateOrganization -p org_id=739224 --jsonata 'id' ```Thanks
", "is_unlicensed_org": false, "style": {}, "type": "Vendor", "symbol": "EPI", "pricing_tier": "professional", "free_user_limit": 50 } ```