Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8950d96
docs(integration-toolkit): add the interactive registration recipe
Oct 9, 2026
d73fd39
docs(integration-toolkit): state the zero-event correlation close
Oct 9, 2026
b737fb7
docs(integration-toolkit): align interactive registration with the fi…
Oct 9, 2026
4c84e24
docs(integration-toolkit): document the parallel close, correlation i…
Oct 9, 2026
24120fc
docs(integration-toolkit): selector values match ignoring case and su…
Oct 13, 2026
561c4ee
docs(integration-toolkit): list the interactive lane monitoring codes
Oct 13, 2026
1fecda9
docs(integration-toolkit): fix the v3 ingest examples
Oct 14, 2026
1c21486
docs(integration-toolkit): state the wait selector encoding contract
Oct 14, 2026
0ae5ba2
docs(integration-toolkit): correct when a wait answers error, timeout…
Oct 14, 2026
5f6687a
docs(integration-toolkit): spell out the close rules, the always mode…
Oct 14, 2026
dd16638
docs(integration-toolkit): document the ingest and read grants and ho…
Oct 14, 2026
ba1ebd2
docs(integration-toolkit): match the examples to what the status API …
Oct 14, 2026
e59dec4
docs(integration-toolkit): regenerate the monitoring codes from the t…
Oct 15, 2026
87bc796
docs(integration-toolkit): document correlation_close and when a zero…
Oct 15, 2026
99d0da8
docs(integration-toolkit): state the selector refusals, the fresh con…
Oct 15, 2026
bf614ca
docs(integration-toolkit): document the bounded status lists, errors_…
Oct 15, 2026
872e86a
docs(integration-toolkit): document the ingest and replay grants and …
Oct 15, 2026
66a7bd9
docs(integration-toolkit): document undecided entity requirements, co…
Oct 15, 2026
d37cbb4
docs(integration-toolkit): a partner's own correlation id needs resul…
Oct 15, 2026
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
66 changes: 51 additions & 15 deletions docs/integrations/integration-toolkit/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v1/integrations/{integra
- `secure_proxy` — Route requests through epilot's secure proxy for static IP or VPN access. See [Secure Proxy Use Cases](#secure-proxy-use-cases).
:::

### Inbound Use Case Options

Beyond the mapping itself, an inbound use case's `configuration` accepts the following options.

| Option | Values | Default | Description |
|--------|--------|---------|-------------|
| `direct` | boolean | `false` | Skip the mapping engine and accept entity-shaped payloads. See [Direct Mode](./inbound/direct-mode.md). |
| `interactive` | `disabled`, `on_request`, `always` | `disabled` | Whether events of this use case may run on the interactive lane. See [Interactive Registration](./interactive-registration.md#enabling-it-on-the-use-case). |

```json title="Allow the request to decide"
{
"entities": [ "..." ],
"interactive": "on_request"
}
```

With `on_request` — the normal setting — one mapping serves both the nightly batch and the
registration push, and the inbound request decides which it is by sending `interactive: true`. With
`disabled`, a request that asks for interactive is processed as bulk and told so per event; nothing
is rejected. With `always`, every event of the use case is interactive — also requests that do not ask
and carry no correlation id — and every one is charged to the organization's interactive budget, so
reserve it for a use case that serves registrations only.

If a caller is going to wait on your use cases by slug (`wait_for_use_cases`), model one inbound use
case per partner event type and give every use case a slug: a use case that maps several kinds of
message cannot tell them apart, and one without a slug cannot be named at all. See
[Interactive Registration](./interactive-registration.md#one-use-case-per-partner-event-type).

### Enabling/Disabling a Use Case

```bash
Expand Down Expand Up @@ -514,15 +542,14 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v1/secure-proxy' \

### Event Message Structure

The structure of a `POST /v3/erp/updates/events` request:

```json
{
"integration_id": "uuid",
"meta": {
"correlation_id": "string"
},
"correlation_id": "string (optional)",
"events": [
{
"event_type": "CREATE | UPDATE | DELETE",
"use_case_slug": "string (recommended, 1-255 chars, pattern: ^[a-z0-9][a-z0-9_-]*$, required if event_name is not provided)",
"event_name": "string (legacy fallback, required if use_case_slug is not provided)",
"timestamp": "ISO 8601 datetime",
Expand All @@ -536,9 +563,15 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v1/secure-proxy' \

For each event, include `use_case_slug` whenever possible. `event_name` is supported as a legacy fallback, and `use_case_slug` takes precedence if both are present.

v3 takes `correlation_id` at request level and has no `event_type`. The deprecated v1 and v2 endpoints take
`event_type` and, on v1, a `meta` object; v3 drops both without an error, so a `meta.correlation_id` sent to v3 is
lost.

`use_case_slug` format: `^[a-z0-9][a-z0-9_-]*$` with length `1..255` (must start with a lowercase letter or digit; then lowercase letters, digits, `_`, `-`).

### Event Types
### Event Types (v1 and v2)

The deprecated v1 and v2 endpoints classify each event with `event_type`. v3 has no such field.

| Type | Description |
|------|-------------|
Expand All @@ -552,7 +585,6 @@ For each event, include `use_case_slug` whenever possible. `event_name` is suppo

```json
{
"event_type": "UPDATE",
"format": "json",
"payload": "{\"customerId\":\"C001\",\"name\":\"John Doe\"}"
}
Expand All @@ -562,7 +594,6 @@ For each event, include `use_case_slug` whenever possible. `event_name` is suppo

```json
{
"event_type": "UPDATE",
"format": "xml",
"payload": "<customer><id>C001</id><name>John Doe</name></customer>"
}
Expand All @@ -578,7 +609,6 @@ Prevent duplicate processing by including a unique deduplication ID:
{
"events": [
{
"event_type": "UPDATE",
"deduplication_id": "customer-C001-update-20240115-001",
"payload": "..."
}
Expand Down Expand Up @@ -607,17 +637,19 @@ customer-C001

### Correlation ID

Include a correlation ID to track related events:
Include a correlation ID at request level to track related events:

```json
{
"meta": {
"correlation_id": "<uuidv4()>"
},
"integration_id": "uuid",
"correlation_id": "<uuidv4()>",
"events": [...]
}
```

When you omit it, epilot generates one and returns it in the response. For a correlation id an end customer is
waiting on, see [Interactive Registration](./interactive-registration.md#correlation-ids).

### Using Correlation IDs

- Group related events for debugging
Expand Down Expand Up @@ -672,6 +704,7 @@ specific integration.
|---|---|
| `integration:view` | Read integrations and use cases; read monitoring events, stats, time series, traces, access logs, notification history and status |
| `integration:manage` | Create, update and delete integrations and use cases; edit notification configuration; push external monitoring events; send test notifications; replay events |
| `integration:ingest` | Push inbound events to the integration (`POST /v1/erp/updates/events`, `/v2/…`, `/v3/…`). It grants nothing else — not reading what landed, and not replay |
| `integration:consume` | Poll and acknowledge the outbound queue. Scoped to [Pollable Outbound](./pollable-outbound.md) only — it grants nothing else |

The `view` / `manage` split is the one that catches people out when minting a scoped
Expand All @@ -684,13 +717,16 @@ token for a middleware:
| `GET /v2/integrations/{id}/notifications/history`, `…/status` | `integration:view` |
| `POST /v2/integrations/{id}/monitoring/external-events` | `integration:manage` |
| `POST /v2/integrations/{id}/notifications/test` | `integration:manage` |
| `POST /v1/integrations/{id}/events/replay` | `integration:manage` |
| `POST /v1/integrations/{id}/events/replay` | `integration:manage`, checked organization-wide |
| `POST /v3/erp/updates/events` (and v1, v2) | `integration:ingest` |
| `GET /v3/erp/updates/events/{event_id}`, `GET /v3/erp/updates/events?correlation_id=…`, `GET /v3/erp/updates/correlations` | `integration:view` |
| `POST /v1/integrations/{id}/outbound/messages/poll`, `…/ack` | `integration:consume` |

The monitoring events list (`POST /v2/integrations/{id}/monitoring/events`) returns
`403` without `integration:view` on that integration. Its event details can carry customer
data — a mapped [poll message](./pollable-outbound.md#what-a-mapped-message-looks-like-in-monitoring)
includes its delivered payload.

Inbound event submission authenticates as the integration's own API token rather
than through these actions — see [Inbound Getting Started](./inbound/getting-started.md).
Inbound event submission needs `integration:ingest` on the integration it pushes to —
see [Inbound Getting Started](./inbound/getting-started.md). A replay of inbound events
needs `integration:manage` instead; a push token's `integration:ingest` does not cover it.
3 changes: 1 addition & 2 deletions docs/integrations/integration-toolkit/inbound/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v3/erp/updates/events' \
-H 'Content-Type: application/json' \
-d '{
"integration_id": "<integration-id>",
"meta": { "correlation_id": "customer-sync-001" },
"correlation_id": "customer-sync-001",
"events": [{
"event_type": "UPDATE",
"use_case_slug": "customer-sync",
"timestamp": "2024-01-15T10:00:00Z",
"format": "json",
Expand Down
29 changes: 21 additions & 8 deletions docs/integrations/integration-toolkit/inbound/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This guide walks you through setting up an inbound integration to synchronize da
## Prerequisites

- Access to the epilot platform with admin permissions
- API credentials for your organization
- API credentials for your organization, with the `integration:ingest` grant on the integration you push to
- Basic understanding of REST APIs and JSON

## Step 1: Create an Integration
Expand Down Expand Up @@ -46,14 +46,16 @@ Save the `id` — you'll need it for subsequent API calls.

## Step 2: Create a Use Case

A use case defines how specific data types are mapped and synchronized. Create an inbound use case for customer data:
A use case defines how specific data types are mapped and synchronized. Create an inbound use case for customer data. The
`slug` is required: it is the key you route events to this use case by in step 3.

```bash title="Create a use case"
curl -X POST 'https://integration-toolkit.sls.epilot.io/v1/integrations/{integrationId}/use-cases' \
-H 'Authorization: Bearer <your-token>' \
-H 'Content-Type: application/json' \
-d '{
"name": "Customer Sync",
"slug": "customer-sync",
"type": "inbound",
"enabled": true,
"configuration": {
Expand Down Expand Up @@ -83,12 +85,9 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v3/erp/updates/events' \
-H 'Content-Type: application/json' \
-d '{
"integration_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"meta": {
"correlation_id": "<uuidv4()>"
},
"correlation_id": "<uuidv4()>",
"events": [
{
"event_type": "UPDATE",
"use_case_slug": "customer-sync",
"timestamp": "2024-01-15T10:35:00Z",
"format": "json",
Expand All @@ -101,29 +100,43 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v3/erp/updates/events' \

Use `use_case_slug` for event routing (recommended). `event_name` remains available for backward compatibility, and `use_case_slug` takes precedence when both are provided. Format: `^[a-z0-9][a-z0-9_-]*$` (1-255 chars; must start with lowercase letter or digit).

`correlation_id` is a request-level field and optional. v3 has no `event_type` and no `meta` object — they belong to
the deprecated v1 and v2 endpoints — and drops both without an error: a `meta.correlation_id` sent to v3 is lost, and
the response carries a generated id instead.

**Response:**

```json title="Event accepted"
{
"correlation_id": "0f5c2a8e-3b7d-4e19-9a64-5d2c8b1e7f30",
"results": [
{
"event_id": "evt_abc123",
"status": "success",
"message": "Event queued for processing"
"event_id": "0199c3f2-8a41-7d0e-b6c7-2f1a9d3e4b55"
}
]
}
```

`correlation_id` echoes the id you sent, or the one epilot generated when you sent none. Each result carries the
`status` of the event (`success`, `skipped` for a duplicate, `ignored` or `error`, the last three with a
`message`) and its `event_id`.

## Processing Flow

Events flow through the following pipeline:

![Processing Flow](/img/integrations/toolkit-processing.svg)

Ingestion is asynchronous: the response means "accepted and queued", not "written". When an end
customer is waiting for the push to land — portal self-registration, adding a contract — send the
portal's correlation id with the request and let the portal wait on it. See
[Interactive Registration](../interactive-registration.md).

## Next Steps

- [Entity Mapping](./mapping.md) - Learn about field mappings and transformations
- [Unique Identifiers](./unique-identifiers.md) - Configure entity lookup strategies
- [Relations](./relations.md) - Link entities together
- [Interactive Registration](../interactive-registration.md) - Push data an end customer is waiting for
- [Examples](./examples.md) - See complete integration examples
8 changes: 8 additions & 0 deletions docs/integrations/integration-toolkit/inbound/relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,14 @@ The `value` field in a relation_ref item supports the same value sources as regu
| `jsonataExpression` | `{ "attribute": "address", "jsonataExpression": "{ \"street\": street, \"city\": city }" }` |
| `constant` | `{ "attribute": "type", "constant": "BILLING" }` |

### An Empty Reference Means No Relation

A relation item is built only when every unique id it is looked up by has a value. When the value
is missing, `null`, an empty string (`""`) or an empty list (`[]`), the item is skipped with a
warning: no relation is set and **no stub entity is created** — the toolkit never looks up or creates
an entity whose unique id is empty. A relation your payload leaves empty on purpose therefore stays
unset, and the event still ends `done`.

## Relation Resolution Strategy

The Integration Toolkit uses an **all-or-nothing** strategy:
Expand Down
Loading
Loading