Enrich
Fan out dependent calls as a DAG — per record or batched — with caching and per-call error policy.
Most list endpoints return thin records. enrich fans out dependent calls to
fill them in — a detail lookup, a related collection, a second-hop join — forming
a DAG that the engine runs level by level. The results are attached to each record
under assignTo and become available to the transform.
enrich:
- id: detail
dependsOn: [list] # the fetch id, or another enrich id
method: GET
path: /api/Deals/{{ record.id }}
cardinality: one # one | many
extract: { dataPath: "$" }
assignTo: _detail
onError: continue
- id: client
dependsOn: [detail] # runs after detail attaches
when: "record._detail.clientId" # skip when falsy
path: /api/Clients/{{ record._detail.clientId }}
extract: { dataPath: "$" }
assignTo: _client
onError: continueFields
| Field | Purpose |
|---|---|
id | Unique call id; referenced by dependsOn. |
dependsOn | One or more upstream ids (fetch or enrich). Defines DAG order. |
when | Optional template predicate — skip the call when it evaluates falsy. |
method / path / query / headers / body | The request (same shape as fetch). |
cardinality | one (attach an object) or many (attach an array). |
batch | Turn N per-record calls into batched calls (see below). |
cache | Memoize identical calls within a scope. |
extract.dataPath | JSONPath into the response to attach. |
assignTo | The record key to attach under (e.g. _detail). |
onError | continue (default) · fail_record · fail_run. |
The DAG is validated acyclic at publish time, and template values resolve
across hops — {{ record._detail.clientId }} works because detail attaches
before client runs.
Two-level joins
Because a later call can depend on data an earlier call attached, you can express
a join the list DTO can't give you directly — fetch the detail (which carries a
clientId), then fetch the client:
list ──▶ detail ──▶ clientA when guard (record._detail.clientId) skips the second hop for records that
have no client, so you don't waste calls on 404s.
Batching
When a source offers a bulk endpoint, batch collapses N per-record calls into
ceil(N / chunkSize) calls. The chunk of keys is exposed as {{ batchKeys }},
and results are matched back to records by matchPath.
- id: contacts
dependsOn: [list]
path: /contacts
query: { ids: "{{ batchKeys }}" }
batch:
keyPath: "$.contactId" # which record field to collect (default $.id)
matchPath: "$.id" # which response field maps back
chunkSize: 100 # max 2000
cardinality: one
extract: { dataPath: "$" }
assignTo: _contactCaching
cache memoizes identical calls so a shared lookup (say, a project referenced by
many deals) is fetched once.
cache:
key: "project:{{ record.projectId }}"
scope: run # run | connection | global (not org)
ttlSeconds: 3600Error policy
onError decides what a failed enrich call does:
| Value | Effect |
|---|---|
continue (default) | Log the error, leave the field unset, keep going. |
fail_record | Drop just this record from the run. |
fail_run | Abort the whole run. |
continue is the default on purpose: a flaky detail endpoint should degrade a few
records, not fail an entire sync. The Run Inspector
still surfaces every enrich error.