diff --git a/.changeset/configuration-hub-sync-conflicts.md b/.changeset/configuration-hub-sync-conflicts.md new file mode 100644 index 00000000..d8628f51 --- /dev/null +++ b/.changeset/configuration-hub-sync-conflicts.md @@ -0,0 +1,7 @@ +--- +"@epilot/configuration-hub-client": minor +"@epilot/sdk": minor +"@epilot/cli": minor +--- + +Refresh the Configuration Hub API contract from the published spec: deterministic sync conflicts on plans (`SyncPlanConflict` with `target_edited`, `both_edited`, `target_only_removed`, `mostly_replaced`; `plan.conflicts`, `summary.conflicts`, `conflicts_checked_at`), the `has_conflicts` filter on `listSyncJobResources`, `overwrite_conflicts` on `applySyncPlan` (409 `unresolved_conflicts`), and the health finding codes `env_value_missing` / `secret_value_missing`. diff --git a/clients/configuration-hub-client/src/openapi-runtime.json b/clients/configuration-hub-client/src/openapi-runtime.json index 99b1c6dc..b813f3ba 100644 --- a/clients/configuration-hub-client/src/openapi-runtime.json +++ b/clients/configuration-hub-client/src/openapi-runtime.json @@ -358,6 +358,10 @@ { "name": "plan_action", "in": "query" + }, + { + "name": "has_conflicts", + "in": "query" } ], "responses": {} diff --git a/clients/configuration-hub-client/src/openapi.d.ts b/clients/configuration-hub-client/src/openapi.d.ts index 37247be7..f5275e36 100644 --- a/clients/configuration-hub-client/src/openapi.d.ts +++ b/clients/configuration-hub-client/src/openapi.d.ts @@ -308,7 +308,7 @@ declare namespace Components { } export interface CreatePreviewRequest { /** - * Display name of the preview. Defaults to `Preview `. + * Display name of the preview. Defaults to `Preview`. */ name?: string; /** @@ -651,6 +651,15 @@ declare namespace Components { * Final status of the copy sync job (`succeeded` or `partial`). */ sync_status?: string; + /** + * Configuration types that could not be listed in the source org (e.g. the + * API answered 403), so the preview has none of them. + * + */ + skipped_types?: { + type: string; + reason: string; + }[]; requested_by: { user_id?: string; email?: string; @@ -1502,6 +1511,10 @@ declare namespace Components { * When a partial replan (`replanSyncPlan` with `resources`) last finished. */ replanned_at?: string; // date-time + /** + * When conflict detection last ran (end of planning or of a replan). + */ + conflicts_checked_at?: string; // date-time source_org_id: string; target_org_id: string; started_at?: string; // date-time @@ -1522,6 +1535,10 @@ declare namespace Components { skip: number; failed: number; not_compared: number; + /** + * Rows with at least one conflict (`plan.conflicts`). + */ + conflicts?: number; type: string; }[]; /** @@ -1548,9 +1565,17 @@ declare namespace Components { /** * Machine-readable reason, for clients that show their own message. */ - code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies"; + code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies" | "unresolved_conflicts"; + /** + * Same as `error` (set with `unresolved_conflicts`). + */ + message?: string; invalid_resources?: SyncJobResourceRef[]; missing_dependencies?: SyncPlanMissingDependency[]; + /** + * With `unresolved_conflicts`, the applied rows with undecided conflicts. + */ + resources?: SyncPlanUnresolvedConflict[]; } export interface SyncPlanApplyRequest { /** @@ -1573,6 +1598,13 @@ declare namespace Components { * Rows of the plan to leave out. */ exclude?: SyncJobResourceRef[]; + /** + * Rows with conflicts to apply anyway, overwriting the target's + * changes. Applied rows with conflicts that are not listed here + * make the request fail with `unresolved_conflicts`. + * + */ + overwrite_conflicts?: SyncJobResourceRef[]; /** * Apply even when left-out resources that would be created are * referenced by applied ones. Those references then keep pointing @@ -1617,6 +1649,177 @@ declare namespace Components { */ after?: any; } + /** + * Target edits since the last sync that applying the row would + * overwrite. Detection compares normalized plan views (the projection + * `getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit + * fields; references rewritten through lineage; array items matched by + * identity), never edit dates alone. + * + * With a stored **base** (the target as the last sync left it): + * three-way, `certainty: exact`. Without one: two-way target vs + * source, `certainty: possible`; values only the target has are + * `target_only_removed`, other differences count as `target_edited` + * only when the target was written after the last sync (plus a short + * grace for the sync's own writes). On a first sync (never synced) + * only target-only content is flagged. These three codes are never + * set on heuristic matches. + * + * `mostly_replaced` is independent of match, lineage and base: the + * update would change or remove at least half of the target's leaf + * fields (see `replaced_ratio`), on a target of at least 10 fields. + * Typically a heuristic match to a different resource. It sorts first + * and can come with the other codes on the same row. + * + */ + export interface SyncPlanConflict { + /** + * `mostly_replaced`: applying the row replaces most of the target + * (`replaced_ratio` >= 0.5); check the counterpart is the right + * one. `certainty` is always `exact`, `paths` empty. + * `target_edited`: the target changed these paths since the last + * sync and the source did not; the update reverts them. + * `both_edited`: both changed the same paths, to different values. + * `target_only_removed`: the update removes items only the target + * has (added there since the last sync, or, without a base, not in + * the source). + * + */ + code: "mostly_replaced" | "target_edited" | "both_edited" | "target_only_removed"; + /** + * `exact`: decided from a stored base. `possible`: no base yet. + */ + certainty: "exact" | "possible"; + /** + * Always true for now. `applySyncPlan` refuses the row until it is overwritten or left out. + */ + requires_decision: boolean; + /** + * The affected paths (at most 50). Values are plan-view values; a + * value whose JSON is over 1 KB is replaced by + * `{ "__truncated": true, "json_bytes": }`. Empty when the + * target changed but the copies could not be compared. + * + */ + paths: [ + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath? + ]; + /** + * True when paths or values were left out. `total_paths` is still exact. + */ + paths_truncated: boolean; + total_paths: number; + /** + * When Config Hub last synced the resource (lineage), when known. + */ + last_synced_at?: string; // date-time + target_updated_at?: string; // date-time + /** + * User id of the target's last editor, when its API exposes one. + */ + target_updated_by?: string; + /** + * Display name of `target_updated_by`, when resolvable cheaply. Not set yet. + */ + target_updated_by_name?: string; + source_updated_at?: string; // date-time + /** + * `mostly_replaced` only: `replaced_fields / total_fields`, rounded + * to two decimals. Show as a percentage, `round(ratio * 100)`. + * + * example: + * 0.92 + */ + replaced_ratio?: number; + /** + * `mostly_replaced` only: leaf fields of the target the apply + * changes or removes (fields only the source has don't count). + * + * example: + * 198 + */ + replaced_fields?: number; + /** + * `mostly_replaced` only: leaf fields of the target's normalized + * plan view. A scalar array is one field; per-org ids (UUIDs + * lineage doesn't know, the org's own id) are not fields. + * + * example: + * 214 + */ + total_fields?: number; + } + export interface SyncPlanConflictPath { + /** + * Same notation as `SyncPlanChange.path`. + */ + path: string; + /** + * The change applying the row would make to the target. + */ + kind: "added" | "removed" | "changed" | "reordered"; + /** + * The value at the last sync. Absent when unknown (always for `possible`). + */ + base?: any; + /** + * The value applying writes. Absent for `removed`. + */ + source?: any; + /** + * The target's current value. Absent for `added`. + */ + target?: any; + } /** * Planned resources by action. `not_compared` counts the `update` * resources whose copies could not be compared (it is part of @@ -1632,6 +1835,10 @@ declare namespace Components { skip: number; failed: number; not_compared: number; + /** + * Rows with at least one conflict (`plan.conflicts`). + */ + conflicts?: number; } export interface SyncPlanDiff { type: string; @@ -1743,6 +1950,12 @@ declare namespace Components { */ resources?: SyncJobResourceRef[]; } + export interface SyncPlanUnresolvedConflict { + type: string; + source_id: string; + title?: string; + codes: ("target_edited" | "both_edited" | "target_only_removed")[]; + } /** * The resource that referenced the dependency. */ @@ -1821,6 +2034,46 @@ declare namespace Components { * Other resources of the plan this resource references. */ depends_on?: SyncJobResourceRef[]; + /** + * Changes made in the target since the last sync that applying + * this `update` would overwrite, or (`mostly_replaced`) an update + * that would replace most of the target. Absent when there are + * none. Detected while planning from normalized diffs (see + * `SyncPlanConflict`); heuristic matches only get + * `mostly_replaced`. + * + */ + conflicts?: /** + * Target edits since the last sync that applying the row would + * overwrite. Detection compares normalized plan views (the projection + * `getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit + * fields; references rewritten through lineage; array items matched by + * identity), never edit dates alone. + * + * With a stored **base** (the target as the last sync left it): + * three-way, `certainty: exact`. Without one: two-way target vs + * source, `certainty: possible`; values only the target has are + * `target_only_removed`, other differences count as `target_edited` + * only when the target was written after the last sync (plus a short + * grace for the sync's own writes). On a first sync (never synced) + * only target-only content is flagged. These three codes are never + * set on heuristic matches. + * + * `mostly_replaced` is independent of match, lineage and base: the + * update would change or remove at least half of the target's leaf + * fields (see `replaced_ratio`), on a target of at least 10 fields. + * Typically a heuristic match to a different resource. It sorts first + * and can come with the other codes on the same row. + * + */ + SyncPlanConflict[]; + /** + * On rows of a job applying a plan, for rows with conflicts: + * `overwrite` when the row was applied anyway (listed in + * `overwrite_conflicts`), `keep_target` when it was left out. + * + */ + conflict_resolution?: "overwrite" | "keep_target"; } } } @@ -2204,6 +2457,7 @@ declare namespace Paths { namespace ListSyncJobResources { namespace Parameters { export type Cursor = string; + export type HasConflicts = boolean; export type Id = string; export type PlanAction = /** * What a real run would do with a planned resource: @@ -2235,6 +2489,7 @@ declare namespace Paths { status?: Parameters.Status; type?: Parameters.Type; plan_action?: Parameters.PlanAction; + has_conflicts?: Parameters.HasConflicts; } namespace Responses { export type $200 = /* Cursor-paginated list of sync job resources. */ Components.Schemas.SyncJobResourceListResponse; @@ -2907,6 +3162,15 @@ export interface OperationMethods { * rejected with 409 and `missing_dependencies`, unless * `allow_missing_dependencies` is set. * + * Rows whose plan has conflicts (`plan.conflicts`: the target was + * changed since the last sync, and applying would overwrite that, or + * applying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway, + * or leave them out (`exclude`, or a `resources` list without them) to + * keep the target's version. Applying one without a decision is + * rejected with 409 and `unresolved_conflicts`; nothing is applied. + * The decision is recorded on the apply job's rows + * (`plan.conflict_resolution`). + * * Applied resources go through the normal sync phases, so a change in * the target since planning is handled like in any sync. A plan can be * applied up to 24 hours after it finished (`expires_at`); plan again @@ -3577,6 +3841,15 @@ export interface PathsDictionary { * rejected with 409 and `missing_dependencies`, unless * `allow_missing_dependencies` is set. * + * Rows whose plan has conflicts (`plan.conflicts`: the target was + * changed since the last sync, and applying would overwrite that, or + * applying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway, + * or leave them out (`exclude`, or a `resources` list without them) to + * keep the target's version. Applying one without a decision is + * rejected with 409 and `unresolved_conflicts`; nothing is applied. + * The decision is recorded on the apply job's rows + * (`plan.conflict_resolution`). + * * Applied resources go through the normal sync phases, so a change in * the target since planning is handled like in any sync. A plan can be * applied up to 24 hours after it finished (`expires_at`); plan again @@ -3817,6 +4090,8 @@ export type SyncPlanAction = Components.Schemas.SyncPlanAction; export type SyncPlanApplyErrorResponse = Components.Schemas.SyncPlanApplyErrorResponse; export type SyncPlanApplyRequest = Components.Schemas.SyncPlanApplyRequest; export type SyncPlanChange = Components.Schemas.SyncPlanChange; +export type SyncPlanConflict = Components.Schemas.SyncPlanConflict; +export type SyncPlanConflictPath = Components.Schemas.SyncPlanConflictPath; export type SyncPlanCounts = Components.Schemas.SyncPlanCounts; export type SyncPlanDiff = Components.Schemas.SyncPlanDiff; export type SyncPlanDiffListResponse = Components.Schemas.SyncPlanDiffListResponse; @@ -3824,5 +4099,6 @@ export type SyncPlanDiffStatus = Components.Schemas.SyncPlanDiffStatus; export type SyncPlanMatch = Components.Schemas.SyncPlanMatch; export type SyncPlanMissingDependency = Components.Schemas.SyncPlanMissingDependency; export type SyncPlanReplanRequest = Components.Schemas.SyncPlanReplanRequest; +export type SyncPlanUnresolvedConflict = Components.Schemas.SyncPlanUnresolvedConflict; export type SyncRequiredBy = Components.Schemas.SyncRequiredBy; export type SyncResourcePlan = Components.Schemas.SyncResourcePlan; diff --git a/clients/configuration-hub-client/src/openapi.json b/clients/configuration-hub-client/src/openapi.json index 198dfd25..05fffb9e 100644 --- a/clients/configuration-hub-client/src/openapi.json +++ b/clients/configuration-hub-client/src/openapi.json @@ -1375,6 +1375,14 @@ "schema": { "$ref": "#/components/schemas/SyncPlanAction" } + }, + { + "name": "has_conflicts", + "in": "query", + "description": "`true`: only rows whose plan has conflicts (`plan.conflicts`), so\na client can load every conflict without paging through the plan.\n", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -1614,7 +1622,7 @@ } ], "summary": "applySyncPlan", - "description": "Run a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\nBy default the job applies every resource the plan would `create` or\n`update`. `no_change` and `skip` resources are not written. Change\nthe selection with:\n- `resources`: apply exactly these rows of the plan. Can include\n `no_change` rows to write them anyway.\n- `exclude`: leave these rows out (applied after `resources`).\n\nRows that are not applied are still listed on the new job, as\n`skipped` with `excluded_from_apply`. Their existing targets keep\nresolving references from the applied resources.\n\nLeaving out a resource the plan would create, while an applied\nresource references it, would leave that reference pointing at a\nresource that does not exist in the target. The request is then\nrejected with 409 and `missing_dependencies`, unless\n`allow_missing_dependencies` is set.\n\nApplied resources go through the normal sync phases, so a change in\nthe target since planning is handled like in any sync. A plan can be\napplied up to 24 hours after it finished (`expires_at`); plan again\nafter that. A plan can be applied more than once; each apply job is\nadded to the plan's `applied_by`.\n\nErrors carry a machine-readable `code`.\n", + "description": "Run a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\nBy default the job applies every resource the plan would `create` or\n`update`. `no_change` and `skip` resources are not written. Change\nthe selection with:\n- `resources`: apply exactly these rows of the plan. Can include\n `no_change` rows to write them anyway.\n- `exclude`: leave these rows out (applied after `resources`).\n\nRows that are not applied are still listed on the new job, as\n`skipped` with `excluded_from_apply`. Their existing targets keep\nresolving references from the applied resources.\n\nLeaving out a resource the plan would create, while an applied\nresource references it, would leave that reference pointing at a\nresource that does not exist in the target. The request is then\nrejected with 409 and `missing_dependencies`, unless\n`allow_missing_dependencies` is set.\n\nRows whose plan has conflicts (`plan.conflicts`: the target was\nchanged since the last sync, and applying would overwrite that, or\napplying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway,\nor leave them out (`exclude`, or a `resources` list without them) to\nkeep the target's version. Applying one without a decision is\nrejected with 409 and `unresolved_conflicts`; nothing is applied.\nThe decision is recorded on the apply job's rows\n(`plan.conflict_resolution`).\n\nApplied resources go through the normal sync phases, so a change in\nthe target since planning is handled like in any sync. A plan can be\napplied up to 24 hours after it finished (`expires_at`); plan again\nafter that. A plan can be applied more than once; each apply job is\nadded to the plan's `applied_by`.\n\nErrors carry a machine-readable `code`.\n", "tags": [ "Sync" ], @@ -1675,7 +1683,7 @@ } }, "409": { - "description": "The job is not a finished plan, it finished more than 24 hours ago,\nthe selection applies nothing, or it leaves out resources that\napplied ones depend on (listed in `missing_dependencies`)\n", + "description": "The job is not a finished plan, it finished more than 24 hours ago,\nthe selection applies nothing, it applies rows with undecided\nconflicts (listed in `resources`, code `unresolved_conflicts`), or\nit leaves out resources that applied ones depend on (listed in\n`missing_dependencies`)\n", "content": { "application/json": { "schema": { @@ -3888,6 +3896,21 @@ "items": { "$ref": "#/components/schemas/SyncJobResourceRef" } + }, + "conflicts": { + "type": "array", + "description": "Changes made in the target since the last sync that applying\nthis `update` would overwrite, or (`mostly_replaced`) an update\nthat would replace most of the target. Absent when there are\nnone. Detected while planning from normalized diffs (see\n`SyncPlanConflict`); heuristic matches only get\n`mostly_replaced`.\n", + "items": { + "$ref": "#/components/schemas/SyncPlanConflict" + } + }, + "conflict_resolution": { + "type": "string", + "description": "On rows of a job applying a plan, for rows with conflicts:\n`overwrite` when the row was applied anyway (listed in\n`overwrite_conflicts`), `keep_target` when it was left out.\n", + "enum": [ + "overwrite", + "keep_target" + ] } }, "required": [ @@ -3895,6 +3918,127 @@ "match" ] }, + "SyncPlanConflict": { + "type": "object", + "description": "Target edits since the last sync that applying the row would\noverwrite. Detection compares normalized plan views (the projection\n`getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit\nfields; references rewritten through lineage; array items matched by\nidentity), never edit dates alone.\n\nWith a stored **base** (the target as the last sync left it):\nthree-way, `certainty: exact`. Without one: two-way target vs\nsource, `certainty: possible`; values only the target has are\n`target_only_removed`, other differences count as `target_edited`\nonly when the target was written after the last sync (plus a short\ngrace for the sync's own writes). On a first sync (never synced)\nonly target-only content is flagged. These three codes are never\nset on heuristic matches.\n\n`mostly_replaced` is independent of match, lineage and base: the\nupdate would change or remove at least half of the target's leaf\nfields (see `replaced_ratio`), on a target of at least 10 fields.\nTypically a heuristic match to a different resource. It sorts first\nand can come with the other codes on the same row.\n", + "properties": { + "code": { + "type": "string", + "description": "`mostly_replaced`: applying the row replaces most of the target\n(`replaced_ratio` >= 0.5); check the counterpart is the right\none. `certainty` is always `exact`, `paths` empty.\n`target_edited`: the target changed these paths since the last\nsync and the source did not; the update reverts them.\n`both_edited`: both changed the same paths, to different values.\n`target_only_removed`: the update removes items only the target\nhas (added there since the last sync, or, without a base, not in\nthe source).\n", + "enum": [ + "mostly_replaced", + "target_edited", + "both_edited", + "target_only_removed" + ] + }, + "certainty": { + "type": "string", + "description": "`exact`: decided from a stored base. `possible`: no base yet.", + "enum": [ + "exact", + "possible" + ] + }, + "requires_decision": { + "type": "boolean", + "description": "Always true for now. `applySyncPlan` refuses the row until it is overwritten or left out." + }, + "paths": { + "type": "array", + "description": "The affected paths (at most 50). Values are plan-view values; a\nvalue whose JSON is over 1 KB is replaced by\n`{ \"__truncated\": true, \"json_bytes\": }`. Empty when the\ntarget changed but the copies could not be compared.\n", + "maxItems": 50, + "items": { + "$ref": "#/components/schemas/SyncPlanConflictPath" + } + }, + "paths_truncated": { + "type": "boolean", + "description": "True when paths or values were left out. `total_paths` is still exact." + }, + "total_paths": { + "type": "integer" + }, + "last_synced_at": { + "type": "string", + "format": "date-time", + "description": "When Config Hub last synced the resource (lineage), when known." + }, + "target_updated_at": { + "type": "string", + "format": "date-time" + }, + "target_updated_by": { + "type": "string", + "description": "User id of the target's last editor, when its API exposes one." + }, + "target_updated_by_name": { + "type": "string", + "description": "Display name of `target_updated_by`, when resolvable cheaply. Not set yet." + }, + "source_updated_at": { + "type": "string", + "format": "date-time" + }, + "replaced_ratio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "`mostly_replaced` only: `replaced_fields / total_fields`, rounded\nto two decimals. Show as a percentage, `round(ratio * 100)`.\n", + "example": 0.92 + }, + "replaced_fields": { + "type": "integer", + "description": "`mostly_replaced` only: leaf fields of the target the apply\nchanges or removes (fields only the source has don't count).\n", + "example": 198 + }, + "total_fields": { + "type": "integer", + "description": "`mostly_replaced` only: leaf fields of the target's normalized\nplan view. A scalar array is one field; per-org ids (UUIDs\nlineage doesn't know, the org's own id) are not fields.\n", + "example": 214 + } + }, + "required": [ + "code", + "certainty", + "requires_decision", + "paths", + "paths_truncated", + "total_paths" + ] + }, + "SyncPlanConflictPath": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Same notation as `SyncPlanChange.path`." + }, + "kind": { + "type": "string", + "description": "The change applying the row would make to the target.", + "enum": [ + "added", + "removed", + "changed", + "reordered" + ] + }, + "base": { + "description": "The value at the last sync. Absent when unknown (always for `possible`)." + }, + "source": { + "description": "The value applying writes. Absent for `removed`." + }, + "target": { + "description": "The target's current value. Absent for `added`." + } + }, + "required": [ + "path", + "kind" + ] + }, "SyncPlanCounts": { "type": "object", "description": "Planned resources by action. `not_compared` counts the `update`\nresources whose copies could not be compared (it is part of\n`update`, not in addition to it). `failed` counts resources planning\nfailed for.\n", @@ -3919,6 +4063,10 @@ }, "not_compared": { "type": "integer" + }, + "conflicts": { + "type": "integer", + "description": "Rows with at least one conflict (`plan.conflicts`)." } }, "required": [ @@ -3963,6 +4111,11 @@ "format": "date-time", "description": "When a partial replan (`replanSyncPlan` with `resources`) last finished." }, + "conflicts_checked_at": { + "type": "string", + "format": "date-time", + "description": "When conflict detection last ran (end of planning or of a replan)." + }, "source_org_id": { "type": "string" }, @@ -4154,6 +4307,13 @@ "$ref": "#/components/schemas/SyncJobResourceRef" } }, + "overwrite_conflicts": { + "type": "array", + "description": "Rows with conflicts to apply anyway, overwriting the target's\nchanges. Applied rows with conflicts that are not listed here\nmake the request fail with `unresolved_conflicts`.\n", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, "allow_missing_dependencies": { "type": "boolean", "default": false, @@ -4241,9 +4401,14 @@ "plan_not_ready", "plan_expired", "nothing_to_apply", - "missing_dependencies" + "missing_dependencies", + "unresolved_conflicts" ] }, + "message": { + "type": "string", + "description": "Same as `error` (set with `unresolved_conflicts`)." + }, "invalid_resources": { "type": "array", "items": { @@ -4255,9 +4420,46 @@ "items": { "$ref": "#/components/schemas/SyncPlanMissingDependency" } + }, + "resources": { + "type": "array", + "description": "With `unresolved_conflicts`, the applied rows with undecided conflicts.", + "items": { + "$ref": "#/components/schemas/SyncPlanUnresolvedConflict" + } } } }, + "SyncPlanUnresolvedConflict": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "codes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "target_edited", + "both_edited", + "target_only_removed" + ] + } + } + }, + "required": [ + "type", + "source_id", + "codes" + ] + }, "DeleteJobStatus": { "type": "string", "description": "Lifecycle status of a bulk-delete job.", @@ -4547,7 +4749,7 @@ "name": { "type": "string", "maxLength": 100, - "description": "Display name of the preview. Defaults to `Preview `." + "description": "Display name of the preview. Defaults to `Preview`." }, "requester": { "type": "object", @@ -4615,6 +4817,25 @@ "type": "string", "description": "Final status of the copy sync job (`succeeded` or `partial`)." }, + "skipped_types": { + "type": "array", + "description": "Configuration types that could not be listed in the source org (e.g. the\nAPI answered 403), so the preview has none of them.\n", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "type", + "reason" + ] + } + }, "requested_by": { "type": "object", "properties": { diff --git a/packages/cli/definitions/configuration-hub.json b/packages/cli/definitions/configuration-hub.json index 198dfd25..05fffb9e 100644 --- a/packages/cli/definitions/configuration-hub.json +++ b/packages/cli/definitions/configuration-hub.json @@ -1375,6 +1375,14 @@ "schema": { "$ref": "#/components/schemas/SyncPlanAction" } + }, + { + "name": "has_conflicts", + "in": "query", + "description": "`true`: only rows whose plan has conflicts (`plan.conflicts`), so\na client can load every conflict without paging through the plan.\n", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -1614,7 +1622,7 @@ } ], "summary": "applySyncPlan", - "description": "Run a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\nBy default the job applies every resource the plan would `create` or\n`update`. `no_change` and `skip` resources are not written. Change\nthe selection with:\n- `resources`: apply exactly these rows of the plan. Can include\n `no_change` rows to write them anyway.\n- `exclude`: leave these rows out (applied after `resources`).\n\nRows that are not applied are still listed on the new job, as\n`skipped` with `excluded_from_apply`. Their existing targets keep\nresolving references from the applied resources.\n\nLeaving out a resource the plan would create, while an applied\nresource references it, would leave that reference pointing at a\nresource that does not exist in the target. The request is then\nrejected with 409 and `missing_dependencies`, unless\n`allow_missing_dependencies` is set.\n\nApplied resources go through the normal sync phases, so a change in\nthe target since planning is handled like in any sync. A plan can be\napplied up to 24 hours after it finished (`expires_at`); plan again\nafter that. A plan can be applied more than once; each apply job is\nadded to the plan's `applied_by`.\n\nErrors carry a machine-readable `code`.\n", + "description": "Run a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\nBy default the job applies every resource the plan would `create` or\n`update`. `no_change` and `skip` resources are not written. Change\nthe selection with:\n- `resources`: apply exactly these rows of the plan. Can include\n `no_change` rows to write them anyway.\n- `exclude`: leave these rows out (applied after `resources`).\n\nRows that are not applied are still listed on the new job, as\n`skipped` with `excluded_from_apply`. Their existing targets keep\nresolving references from the applied resources.\n\nLeaving out a resource the plan would create, while an applied\nresource references it, would leave that reference pointing at a\nresource that does not exist in the target. The request is then\nrejected with 409 and `missing_dependencies`, unless\n`allow_missing_dependencies` is set.\n\nRows whose plan has conflicts (`plan.conflicts`: the target was\nchanged since the last sync, and applying would overwrite that, or\napplying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway,\nor leave them out (`exclude`, or a `resources` list without them) to\nkeep the target's version. Applying one without a decision is\nrejected with 409 and `unresolved_conflicts`; nothing is applied.\nThe decision is recorded on the apply job's rows\n(`plan.conflict_resolution`).\n\nApplied resources go through the normal sync phases, so a change in\nthe target since planning is handled like in any sync. A plan can be\napplied up to 24 hours after it finished (`expires_at`); plan again\nafter that. A plan can be applied more than once; each apply job is\nadded to the plan's `applied_by`.\n\nErrors carry a machine-readable `code`.\n", "tags": [ "Sync" ], @@ -1675,7 +1683,7 @@ } }, "409": { - "description": "The job is not a finished plan, it finished more than 24 hours ago,\nthe selection applies nothing, or it leaves out resources that\napplied ones depend on (listed in `missing_dependencies`)\n", + "description": "The job is not a finished plan, it finished more than 24 hours ago,\nthe selection applies nothing, it applies rows with undecided\nconflicts (listed in `resources`, code `unresolved_conflicts`), or\nit leaves out resources that applied ones depend on (listed in\n`missing_dependencies`)\n", "content": { "application/json": { "schema": { @@ -3888,6 +3896,21 @@ "items": { "$ref": "#/components/schemas/SyncJobResourceRef" } + }, + "conflicts": { + "type": "array", + "description": "Changes made in the target since the last sync that applying\nthis `update` would overwrite, or (`mostly_replaced`) an update\nthat would replace most of the target. Absent when there are\nnone. Detected while planning from normalized diffs (see\n`SyncPlanConflict`); heuristic matches only get\n`mostly_replaced`.\n", + "items": { + "$ref": "#/components/schemas/SyncPlanConflict" + } + }, + "conflict_resolution": { + "type": "string", + "description": "On rows of a job applying a plan, for rows with conflicts:\n`overwrite` when the row was applied anyway (listed in\n`overwrite_conflicts`), `keep_target` when it was left out.\n", + "enum": [ + "overwrite", + "keep_target" + ] } }, "required": [ @@ -3895,6 +3918,127 @@ "match" ] }, + "SyncPlanConflict": { + "type": "object", + "description": "Target edits since the last sync that applying the row would\noverwrite. Detection compares normalized plan views (the projection\n`getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit\nfields; references rewritten through lineage; array items matched by\nidentity), never edit dates alone.\n\nWith a stored **base** (the target as the last sync left it):\nthree-way, `certainty: exact`. Without one: two-way target vs\nsource, `certainty: possible`; values only the target has are\n`target_only_removed`, other differences count as `target_edited`\nonly when the target was written after the last sync (plus a short\ngrace for the sync's own writes). On a first sync (never synced)\nonly target-only content is flagged. These three codes are never\nset on heuristic matches.\n\n`mostly_replaced` is independent of match, lineage and base: the\nupdate would change or remove at least half of the target's leaf\nfields (see `replaced_ratio`), on a target of at least 10 fields.\nTypically a heuristic match to a different resource. It sorts first\nand can come with the other codes on the same row.\n", + "properties": { + "code": { + "type": "string", + "description": "`mostly_replaced`: applying the row replaces most of the target\n(`replaced_ratio` >= 0.5); check the counterpart is the right\none. `certainty` is always `exact`, `paths` empty.\n`target_edited`: the target changed these paths since the last\nsync and the source did not; the update reverts them.\n`both_edited`: both changed the same paths, to different values.\n`target_only_removed`: the update removes items only the target\nhas (added there since the last sync, or, without a base, not in\nthe source).\n", + "enum": [ + "mostly_replaced", + "target_edited", + "both_edited", + "target_only_removed" + ] + }, + "certainty": { + "type": "string", + "description": "`exact`: decided from a stored base. `possible`: no base yet.", + "enum": [ + "exact", + "possible" + ] + }, + "requires_decision": { + "type": "boolean", + "description": "Always true for now. `applySyncPlan` refuses the row until it is overwritten or left out." + }, + "paths": { + "type": "array", + "description": "The affected paths (at most 50). Values are plan-view values; a\nvalue whose JSON is over 1 KB is replaced by\n`{ \"__truncated\": true, \"json_bytes\": }`. Empty when the\ntarget changed but the copies could not be compared.\n", + "maxItems": 50, + "items": { + "$ref": "#/components/schemas/SyncPlanConflictPath" + } + }, + "paths_truncated": { + "type": "boolean", + "description": "True when paths or values were left out. `total_paths` is still exact." + }, + "total_paths": { + "type": "integer" + }, + "last_synced_at": { + "type": "string", + "format": "date-time", + "description": "When Config Hub last synced the resource (lineage), when known." + }, + "target_updated_at": { + "type": "string", + "format": "date-time" + }, + "target_updated_by": { + "type": "string", + "description": "User id of the target's last editor, when its API exposes one." + }, + "target_updated_by_name": { + "type": "string", + "description": "Display name of `target_updated_by`, when resolvable cheaply. Not set yet." + }, + "source_updated_at": { + "type": "string", + "format": "date-time" + }, + "replaced_ratio": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "`mostly_replaced` only: `replaced_fields / total_fields`, rounded\nto two decimals. Show as a percentage, `round(ratio * 100)`.\n", + "example": 0.92 + }, + "replaced_fields": { + "type": "integer", + "description": "`mostly_replaced` only: leaf fields of the target the apply\nchanges or removes (fields only the source has don't count).\n", + "example": 198 + }, + "total_fields": { + "type": "integer", + "description": "`mostly_replaced` only: leaf fields of the target's normalized\nplan view. A scalar array is one field; per-org ids (UUIDs\nlineage doesn't know, the org's own id) are not fields.\n", + "example": 214 + } + }, + "required": [ + "code", + "certainty", + "requires_decision", + "paths", + "paths_truncated", + "total_paths" + ] + }, + "SyncPlanConflictPath": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Same notation as `SyncPlanChange.path`." + }, + "kind": { + "type": "string", + "description": "The change applying the row would make to the target.", + "enum": [ + "added", + "removed", + "changed", + "reordered" + ] + }, + "base": { + "description": "The value at the last sync. Absent when unknown (always for `possible`)." + }, + "source": { + "description": "The value applying writes. Absent for `removed`." + }, + "target": { + "description": "The target's current value. Absent for `added`." + } + }, + "required": [ + "path", + "kind" + ] + }, "SyncPlanCounts": { "type": "object", "description": "Planned resources by action. `not_compared` counts the `update`\nresources whose copies could not be compared (it is part of\n`update`, not in addition to it). `failed` counts resources planning\nfailed for.\n", @@ -3919,6 +4063,10 @@ }, "not_compared": { "type": "integer" + }, + "conflicts": { + "type": "integer", + "description": "Rows with at least one conflict (`plan.conflicts`)." } }, "required": [ @@ -3963,6 +4111,11 @@ "format": "date-time", "description": "When a partial replan (`replanSyncPlan` with `resources`) last finished." }, + "conflicts_checked_at": { + "type": "string", + "format": "date-time", + "description": "When conflict detection last ran (end of planning or of a replan)." + }, "source_org_id": { "type": "string" }, @@ -4154,6 +4307,13 @@ "$ref": "#/components/schemas/SyncJobResourceRef" } }, + "overwrite_conflicts": { + "type": "array", + "description": "Rows with conflicts to apply anyway, overwriting the target's\nchanges. Applied rows with conflicts that are not listed here\nmake the request fail with `unresolved_conflicts`.\n", + "items": { + "$ref": "#/components/schemas/SyncJobResourceRef" + } + }, "allow_missing_dependencies": { "type": "boolean", "default": false, @@ -4241,9 +4401,14 @@ "plan_not_ready", "plan_expired", "nothing_to_apply", - "missing_dependencies" + "missing_dependencies", + "unresolved_conflicts" ] }, + "message": { + "type": "string", + "description": "Same as `error` (set with `unresolved_conflicts`)." + }, "invalid_resources": { "type": "array", "items": { @@ -4255,9 +4420,46 @@ "items": { "$ref": "#/components/schemas/SyncPlanMissingDependency" } + }, + "resources": { + "type": "array", + "description": "With `unresolved_conflicts`, the applied rows with undecided conflicts.", + "items": { + "$ref": "#/components/schemas/SyncPlanUnresolvedConflict" + } } } }, + "SyncPlanUnresolvedConflict": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "source_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "codes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "target_edited", + "both_edited", + "target_only_removed" + ] + } + } + }, + "required": [ + "type", + "source_id", + "codes" + ] + }, "DeleteJobStatus": { "type": "string", "description": "Lifecycle status of a bulk-delete job.", @@ -4547,7 +4749,7 @@ "name": { "type": "string", "maxLength": 100, - "description": "Display name of the preview. Defaults to `Preview `." + "description": "Display name of the preview. Defaults to `Preview`." }, "requester": { "type": "object", @@ -4615,6 +4817,25 @@ "type": "string", "description": "Final status of the copy sync job (`succeeded` or `partial`)." }, + "skipped_types": { + "type": "array", + "description": "Configuration types that could not be listed in the source org (e.g. the\nAPI answered 403), so the preview has none of them.\n", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "type", + "reason" + ] + } + }, "requested_by": { "type": "object", "properties": { diff --git a/packages/cli/docs/configuration-hub.md b/packages/cli/docs/configuration-hub.md index efc5e7a1..b35a2ff0 100644 --- a/packages/cli/docs/configuration-hub.md +++ b/packages/cli/docs/configuration-hub.md @@ -994,7 +994,8 @@ epilot configuration-hub createSyncJob --jsonata 'id' "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1119,7 +1120,8 @@ epilot configuration-hub getSyncJob -p id=123e4567-e89b-12d3-a456-426614174000 - "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1369,7 +1371,8 @@ epilot configuration-hub retrySyncJob -p id=123e4567-e89b-12d3-a456-426614174000 "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1449,12 +1452,7 @@ epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-42661417 "source_id": "string", "target_id": "string", "title": "string", - "required_by": { - "type": "string", - "source_id": "string", - "title": "string", - "reason": "typed_reference" - }, + "required_by": {}, "status": "pending", "phase": "phase_0", "attempt": 0, @@ -1462,20 +1460,7 @@ epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-42661417 "updated_at": "1970-01-01T00:00:00.000Z", "source_link": "string", "target_link": "string", - "plan": { - "action": "create", - "match": "lineage", - "diff_status": "computed", - "change_count": 0, - "changed_paths": ["string"], - "note": "string", - "depends_on": [ - { - "type": "string", - "source_id": "string" - } - ] - }, + "plan": {}, "excluded_from_apply": "unchanged" } ], @@ -1485,12 +1470,7 @@ epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-42661417 "source_id": "string", "target_id": "string", "title": "string", - "required_by": { - "type": "string", - "source_id": "string", - "title": "string", - "reason": "typed_reference" - }, + "required_by": {}, "status": "pending", "phase": "phase_0", "attempt": 0, @@ -1498,20 +1478,7 @@ epilot configuration-hub planSyncJobRetry -p id=123e4567-e89b-12d3-a456-42661417 "updated_at": "1970-01-01T00:00:00.000Z", "source_link": "string", "target_link": "string", - "plan": { - "action": "create", - "match": "lineage", - "diff_status": "computed", - "change_count": 0, - "changed_paths": ["string"], - "note": "string", - "depends_on": [ - { - "type": "string", - "source_id": "string" - } - ] - }, + "plan": {}, "excluded_from_apply": "unchanged" } ], @@ -1619,7 +1586,8 @@ epilot configuration-hub cancelSyncJob -p id=123e4567-e89b-12d3-a456-42661417400 "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1750,6 +1718,9 @@ List the per-resource rows for a sync job. Supports filtering by status | `plan_action` | query | "create" \| "update" \| "no_change" \| "skip" | No | Filter resources by plan action (`plan.action`). Only rows of plan jobs and of jobs applying a plan carry one. | +| `has_conflicts` | query | boolean | No | `true`: only rows whose plan has conflicts (`plan.conflicts`), so +a client can load every conflict without paging through the plan. + | **Sample Call** @@ -1807,7 +1778,34 @@ epilot configuration-hub listSyncJobResources -p id=123e4567-e89b-12d3-a456-4266 "type": "string", "source_id": "string" } - ] + ], + "conflicts": [ + { + "code": "mostly_replaced", + "certainty": "exact", + "requires_decision": true, + "paths": [ + { + "path": "string", + "kind": "added", + "base": {}, + "source": {}, + "target": {} + } + ], + "paths_truncated": true, + "total_paths": 0, + "last_synced_at": "1970-01-01T00:00:00.000Z", + "target_updated_at": "1970-01-01T00:00:00.000Z", + "target_updated_by": "string", + "target_updated_by_name": "string", + "source_updated_at": "1970-01-01T00:00:00.000Z", + "replaced_ratio": 0.92, + "replaced_fields": 198, + "total_fields": 214 + } + ], + "conflict_resolution": "overwrite" }, "excluded_from_apply": "unchanged" } @@ -1862,6 +1860,7 @@ epilot configuration-hub getSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 "expires_at": "1970-01-01T00:00:00.000Z", "planned_at": "1970-01-01T00:00:00.000Z", "replanned_at": "1970-01-01T00:00:00.000Z", + "conflicts_checked_at": "1970-01-01T00:00:00.000Z", "source_org_id": "string", "target_org_id": "string", "started_at": "1970-01-01T00:00:00.000Z", @@ -1873,7 +1872,8 @@ epilot configuration-hub getSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "by_type": [ { @@ -1884,6 +1884,7 @@ epilot configuration-hub getSyncPlan -p id=123e4567-e89b-12d3-a456-426614174000 "skip": 0, "failed": 0, "not_compared": 0, + "conflicts": 0, "type": "string" } ], @@ -2073,6 +2074,12 @@ epilot configuration-hub applySyncPlan \ "source_id": "string" } ], + "overwrite_conflicts": [ + { + "type": "string", + "source_id": "string" + } + ], "allow_missing_dependencies": false }' ``` @@ -2160,7 +2167,8 @@ epilot configuration-hub applySyncPlan -p id=123e4567-e89b-12d3-a456-42661417400 "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -2578,6 +2586,12 @@ epilot configuration-hub listPreviews --jsonata 'results[0]' "preview_org_id": "string", "sync_job_id": "string", "sync_status": "string", + "skipped_types": [ + { + "type": "string", + "reason": "string" + } + ], "requested_by": { "user_id": "string", "email": "string" @@ -2666,6 +2680,12 @@ epilot configuration-hub getPreview -p id=123e4567-e89b-12d3-a456-426614174000 - "preview_org_id": "string", "sync_job_id": "string", "sync_status": "string", + "skipped_types": [ + { + "type": "string", + "reason": "string" + } + ], "requested_by": { "user_id": "string", "email": "string" diff --git a/packages/epilot-sdk-v2/docs/configuration-hub.md b/packages/epilot-sdk-v2/docs/configuration-hub.md index fcec0551..cf1f47f9 100644 --- a/packages/epilot-sdk-v2/docs/configuration-hub.md +++ b/packages/epilot-sdk-v2/docs/configuration-hub.md @@ -126,6 +126,8 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`SyncPlanMatch`](#syncplanmatch) - [`SyncPlanDiffStatus`](#syncplandiffstatus) - [`SyncResourcePlan`](#syncresourceplan) +- [`SyncPlanConflict`](#syncplanconflict) +- [`SyncPlanConflictPath`](#syncplanconflictpath) - [`SyncPlanCounts`](#syncplancounts) - [`SyncPlan`](#syncplan) - [`SyncPlanChange`](#syncplanchange) @@ -135,6 +137,7 @@ const { data } = await configurationHubClient.listConfigTypes(...) - [`SyncPlanReplanRequest`](#syncplanreplanrequest) - [`SyncPlanMissingDependency`](#syncplanmissingdependency) - [`SyncPlanApplyErrorResponse`](#syncplanapplyerrorresponse) +- [`SyncPlanUnresolvedConflict`](#syncplanunresolvedconflict) - [`DeleteJobStatus`](#deletejobstatus) - [`DeleteJobResourceStatus`](#deletejobresourcestatus) - [`DeleteJobCounts`](#deletejobcounts) @@ -836,7 +839,8 @@ const { data } = await client.createSyncJob( "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -957,7 +961,8 @@ const { data } = await client.listSyncJobs({ "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1076,7 +1081,8 @@ const { data } = await client.getSyncJob({ "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1287,7 +1293,8 @@ const { data } = await client.retrySyncJob( "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1351,12 +1358,7 @@ const { data } = await client.planSyncJobRetry( "source_id": "string", "target_id": "string", "title": "string", - "required_by": { - "type": "string", - "source_id": "string", - "title": "string", - "reason": "typed_reference" - }, + "required_by": {}, "status": "pending", "phase": "phase_0", "attempt": 0, @@ -1364,20 +1366,7 @@ const { data } = await client.planSyncJobRetry( "updated_at": "1970-01-01T00:00:00.000Z", "source_link": "string", "target_link": "string", - "plan": { - "action": "create", - "match": "lineage", - "diff_status": "computed", - "change_count": 0, - "changed_paths": ["string"], - "note": "string", - "depends_on": [ - { - "type": "string", - "source_id": "string" - } - ] - }, + "plan": {}, "excluded_from_apply": "unchanged" } ], @@ -1387,12 +1376,7 @@ const { data } = await client.planSyncJobRetry( "source_id": "string", "target_id": "string", "title": "string", - "required_by": { - "type": "string", - "source_id": "string", - "title": "string", - "reason": "typed_reference" - }, + "required_by": {}, "status": "pending", "phase": "phase_0", "attempt": 0, @@ -1400,20 +1384,7 @@ const { data } = await client.planSyncJobRetry( "updated_at": "1970-01-01T00:00:00.000Z", "source_link": "string", "target_link": "string", - "plan": { - "action": "create", - "match": "lineage", - "diff_status": "computed", - "change_count": 0, - "changed_paths": ["string"], - "note": "string", - "depends_on": [ - { - "type": "string", - "source_id": "string" - } - ] - }, + "plan": {}, "excluded_from_apply": "unchanged" } ], @@ -1513,7 +1484,8 @@ const { data } = await client.cancelSyncJob({ "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -1619,6 +1591,7 @@ const { data } = await client.listSyncJobResources({ status: 'example', type: 'example', plan_action: 'example', + has_conflicts: true, }) ``` @@ -1659,7 +1632,34 @@ const { data } = await client.listSyncJobResources({ "type": "string", "source_id": "string" } - ] + ], + "conflicts": [ + { + "code": "mostly_replaced", + "certainty": "exact", + "requires_decision": true, + "paths": [ + { + "path": "string", + "kind": "added", + "base": {}, + "source": {}, + "target": {} + } + ], + "paths_truncated": true, + "total_paths": 0, + "last_synced_at": "1970-01-01T00:00:00.000Z", + "target_updated_at": "1970-01-01T00:00:00.000Z", + "target_updated_by": "string", + "target_updated_by_name": "string", + "source_updated_at": "1970-01-01T00:00:00.000Z", + "replaced_ratio": 0.92, + "replaced_fields": 198, + "total_fields": 214 + } + ], + "conflict_resolution": "overwrite" }, "excluded_from_apply": "unchanged" } @@ -1697,6 +1697,7 @@ const { data } = await client.getSyncPlan({ "expires_at": "1970-01-01T00:00:00.000Z", "planned_at": "1970-01-01T00:00:00.000Z", "replanned_at": "1970-01-01T00:00:00.000Z", + "conflicts_checked_at": "1970-01-01T00:00:00.000Z", "source_org_id": "string", "target_org_id": "string", "started_at": "1970-01-01T00:00:00.000Z", @@ -1708,7 +1709,8 @@ const { data } = await client.getSyncPlan({ "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "by_type": [ { @@ -1719,6 +1721,7 @@ const { data } = await client.getSyncPlan({ "skip": 0, "failed": 0, "not_compared": 0, + "conflicts": 0, "type": "string" } ], @@ -1857,6 +1860,12 @@ const { data } = await client.applySyncPlan( source_id: 'string' } ], + overwrite_conflicts: [ + { + type: 'string', + source_id: 'string' + } + ], allow_missing_dependencies: false }, ) @@ -1936,7 +1945,8 @@ const { data } = await client.applySyncPlan( "no_change": 0, "skip": 0, "failed": 0, - "not_compared": 0 + "not_compared": 0, + "conflicts": 0 }, "planned_at": "1970-01-01T00:00:00.000Z", "expires_at": "1970-01-01T00:00:00.000Z", @@ -2289,6 +2299,12 @@ const { data } = await client.listPreviews() "preview_org_id": "string", "sync_job_id": "string", "sync_status": "string", + "skipped_types": [ + { + "type": "string", + "reason": "string" + } + ], "requested_by": { "user_id": "string", "email": "string" @@ -2329,6 +2345,12 @@ const { data } = await client.getPreview({ "preview_org_id": "string", "sync_job_id": "string", "sync_status": "string", + "skipped_types": [ + { + "type": "string", + "reason": "string" + } + ], "requested_by": { "user_id": "string", "email": "string" @@ -2926,6 +2948,8 @@ type SyncJobRetryPlan = { changed_paths?: { ... } note?: { ... } depends_on?: { ... } + conflicts?: { ... } + conflict_resolution?: { ... } } excluded_from_apply?: "unchanged" | "excluded" | "not_syncable" }> @@ -2955,6 +2979,8 @@ type SyncJobRetryPlan = { changed_paths?: { ... } note?: { ... } depends_on?: { ... } + conflicts?: { ... } + conflict_resolution?: { ... } } excluded_from_apply?: "unchanged" | "excluded" | "not_syncable" }> @@ -3290,6 +3316,7 @@ type SyncJob = { skip: number failed: number not_compared: number + conflicts?: number } planned_at?: string // date-time expires_at?: string // date-time @@ -3373,6 +3400,7 @@ type SyncJobListResponse = { skip: { ... } failed: { ... } not_compared: { ... } + conflicts?: { ... } } planned_at?: string // date-time expires_at?: string // date-time @@ -3430,6 +3458,23 @@ type SyncJobResource = { type: { ... } source_id: { ... } }> + conflicts?: Array<{ + code: { ... } + certainty: { ... } + requires_decision: { ... } + paths: { ... } + paths_truncated: { ... } + total_paths: { ... } + last_synced_at?: { ... } + target_updated_at?: { ... } + target_updated_by?: { ... } + target_updated_by_name?: { ... } + source_updated_at?: { ... } + replaced_ratio?: { ... } + replaced_fields?: { ... } + total_fields?: { ... } + }> + conflict_resolution?: "overwrite" | "keep_target" } excluded_from_apply?: "unchanged" | "excluded" | "not_syncable" } @@ -3468,6 +3513,8 @@ type SyncJobResourceListResponse = { changed_paths?: { ... } note?: { ... } depends_on?: { ... } + conflicts?: { ... } + conflict_resolution?: { ... } } excluded_from_apply?: "unchanged" | "excluded" | "not_syncable" }> @@ -3526,6 +3573,76 @@ type SyncResourcePlan = { type: string source_id: string }> + conflicts?: Array<{ + code: "mostly_replaced" | "target_edited" | "both_edited" | "target_only_removed" + certainty: "exact" | "possible" + requires_decision: boolean + paths: Array<{ + path: { ... } + kind: { ... } + base?: { ... } + source?: { ... } + target?: { ... } + }> + paths_truncated: boolean + total_paths: number + last_synced_at?: string // date-time + target_updated_at?: string // date-time + target_updated_by?: string + target_updated_by_name?: string + source_updated_at?: string // date-time + replaced_ratio?: number + replaced_fields?: number + total_fields?: number + }> + conflict_resolution?: "overwrite" | "keep_target" +} +``` + +### `SyncPlanConflict` + +Target edits since the last sync that applying the row would +overwrite. Detection compares normalized plan views (the projection +`getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit +fields; references rewritten through lineage; array items matched by +identity), never edit dates alone. + +W + +```ts +type SyncPlanConflict = { + code: "mostly_replaced" | "target_edited" | "both_edited" | "target_only_removed" + certainty: "exact" | "possible" + requires_decision: boolean + paths: Array<{ + path: string + kind: "added" | "removed" | "changed" | "reordered" + base?: unknown + source?: unknown + target?: unknown + }> + paths_truncated: boolean + total_paths: number + last_synced_at?: string // date-time + target_updated_at?: string // date-time + target_updated_by?: string + target_updated_by_name?: string + source_updated_at?: string // date-time + replaced_ratio?: number + replaced_fields?: number + total_fields?: number +} +``` + +### `SyncPlanConflictPath` + +```ts +type SyncPlanConflictPath = { + path: string + kind: "added" | "removed" | "changed" | "reordered" + base?: unknown + source?: unknown + target?: unknown } ``` @@ -3546,6 +3663,7 @@ type SyncPlanCounts = { skip: number failed: number not_compared: number + conflicts?: number } ``` @@ -3560,6 +3678,7 @@ type SyncPlan = { expires_at?: string // date-time planned_at?: string // date-time replanned_at?: string // date-time + conflicts_checked_at?: string // date-time source_org_id: string target_org_id: string started_at?: string // date-time @@ -3572,6 +3691,7 @@ type SyncPlan = { skip: number failed: number not_compared: number + conflicts?: number } by_type: Array<{ total: number @@ -3581,6 +3701,7 @@ type SyncPlan = { skip: number failed: number not_compared: number + conflicts?: number type: string }> applied_by?: string[] @@ -3671,6 +3792,10 @@ type SyncPlanApplyRequest = { type: string source_id: string }> + overwrite_conflicts?: Array<{ + type: string + source_id: string + }> allow_missing_dependencies?: boolean } ``` @@ -3708,7 +3833,8 @@ type SyncPlanMissingDependency = { type SyncPlanApplyErrorResponse = { status?: number error?: string - code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies" + code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies" | "unresolved_conflicts" + message?: string invalid_resources?: Array<{ type: string source_id: string @@ -3723,6 +3849,23 @@ type SyncPlanApplyErrorResponse = { title?: { ... } }> }> + resources?: Array<{ + type: string + source_id: string + title?: string + codes: "target_edited" | "both_edited" | "target_only_removed"[] + }> +} +``` + +### `SyncPlanUnresolvedConflict` + +```ts +type SyncPlanUnresolvedConflict = { + type: string + source_id: string + title?: string + codes: "target_edited" | "both_edited" | "target_only_removed"[] } ``` @@ -3941,6 +4084,10 @@ type Preview = { preview_org_id?: string sync_job_id?: string sync_status?: string + skipped_types?: Array<{ + type: string + reason: string + }> requested_by: { user_id?: string email?: string diff --git a/packages/epilot-sdk-v2/src/definitions/configuration-hub-runtime.json b/packages/epilot-sdk-v2/src/definitions/configuration-hub-runtime.json index eebeb241..7ed99cc8 100644 --- a/packages/epilot-sdk-v2/src/definitions/configuration-hub-runtime.json +++ b/packages/epilot-sdk-v2/src/definitions/configuration-hub-runtime.json @@ -1 +1 @@ -{"s":"https://configuration-hub.sls.epilot.io","o":[["listConfigTypes","get","/v1/configs/types"],["listConfigs","get","/v1/configs/{type}",[["ConfigType"],["CursorParam"],["SizeParam"],["q","q"],["updated_after","q"],["updated_before","q"],["purposes","q"],["blueprint_ids","q"],["sort","q"],["active_only","q"]]],["getConfigDependencies","get","/v1/configs/{type}/{id}/dependencies",[["ConfigType"],["ConfigId"],["CursorParam"],["SizeParam"]]],["getConfigUsedBy","get","/v1/configs/{type}/{id}/used_by",[["ConfigType"],["ConfigId"]]],["getIndex","get","/v1/configs/index"],["startHealthScan","post","/v1/configs/health:scan"],["getHealthReport","get","/v1/configs/health"],["requestHealthCoverage","post","/v1/configs/health:coverage-request",null,1],["startHealthCleanup","post","/v1/configs/health:cleanup",null,1],["setHealthFindingsIgnored","post","/v1/configs/health:ignore",null,1],["compareConfigs","post","/v1/configs/compare",null,1],["suggestMatches","post","/v1/configs/compare/suggestions",null,1],["confirmLineage","post","/v1/configs/lineage",null,1],["breakLineage","delete","/v1/configs/lineage",[["type","q",true],["source_id","q",true]]],["createSyncJob","post","/v1/configs/sync-jobs",null,1],["listSyncJobs","get","/v1/configs/sync-jobs",[["CursorParam"],["SizeParam"],["status","q"]]],["getSyncJob","get","/v1/configs/sync-jobs/{id}",[["SyncJobId"]]],["getSyncJobHealth","get","/v1/configs/sync-jobs/{id}/health",[["SyncJobId"]]],["runSyncJobHealth","post","/v1/configs/sync-jobs/{id}/health:run",[["SyncJobId"]]],["retrySyncJob","post","/v1/configs/sync-jobs/{id}/retry",[["SyncJobId"]],1],["planSyncJobRetry","post","/v1/configs/sync-jobs/{id}/retry-plan",[["SyncJobId"]],1],["cancelSyncJob","post","/v1/configs/sync-jobs/{id}/cancel",[["SyncJobId"]]],["listSyncJobEvents","get","/v1/configs/sync-jobs/{id}/events",[["SyncJobId"],["CursorParam"],["size","q"],["after","q"],["kind","q"]]],["listSyncJobResources","get","/v1/configs/sync-jobs/{id}/resources",[["SyncJobId"],["CursorParam"],["size","q"],["status","q"],["type","q"],["plan_action","q"]]],["getSyncPlan","get","/v1/configs/sync-jobs/{id}/plan",[["SyncJobId"]]],["listSyncPlanDiffs","get","/v1/configs/sync-jobs/{id}/plan/diffs",[["SyncJobId"],["CursorParam"],["size","q"],["type","q"],["action","q"]]],["getSyncPlanDiff","get","/v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}",[["SyncJobId"],["type","p",true],["source_id","p",true]]],["applySyncPlan","post","/v1/configs/sync-jobs/{id}/plan/apply",[["SyncJobId"]],1],["replanSyncPlan","post","/v1/configs/sync-jobs/{id}/plan/replan",[["SyncJobId"]],1],["createDeleteJob","post","/v1/configs/delete-jobs",null,1],["listDeleteJobs","get","/v1/configs/delete-jobs",[["CursorParam"],["SizeParam"]]],["getDeleteJob","get","/v1/configs/delete-jobs/{id}",[["DeleteJobId"]]],["listDeleteJobResources","get","/v1/configs/delete-jobs/{id}/resources",[["DeleteJobId"],["CursorParam"],["SizeParam"]]],["rebuildIndex","post","/v1/configs/index:rebuild"],["getConfigInventory","get","/v1/configs/inventory",[["include_dependency_only","q"]]],["createPreview","post","/v1/previews",null,1],["listPreviews","get","/v1/previews"],["getPreview","get","/v1/previews/{id}",[["id","p",true]]]],"v":"3.0.3","cp":{"ConfigType":["type","p",true],"ConfigId":["id","p",true],"CursorParam":["cursor","q"],"SizeParam":["size","q"],"SyncJobId":["id","p",true],"DeleteJobId":["id","p",true]}} \ No newline at end of file +{"s":"https://configuration-hub.sls.epilot.io","o":[["listConfigTypes","get","/v1/configs/types"],["listConfigs","get","/v1/configs/{type}",[["ConfigType"],["CursorParam"],["SizeParam"],["q","q"],["updated_after","q"],["updated_before","q"],["purposes","q"],["blueprint_ids","q"],["sort","q"],["active_only","q"]]],["getConfigDependencies","get","/v1/configs/{type}/{id}/dependencies",[["ConfigType"],["ConfigId"],["CursorParam"],["SizeParam"]]],["getConfigUsedBy","get","/v1/configs/{type}/{id}/used_by",[["ConfigType"],["ConfigId"]]],["getIndex","get","/v1/configs/index"],["startHealthScan","post","/v1/configs/health:scan"],["getHealthReport","get","/v1/configs/health"],["requestHealthCoverage","post","/v1/configs/health:coverage-request",null,1],["startHealthCleanup","post","/v1/configs/health:cleanup",null,1],["setHealthFindingsIgnored","post","/v1/configs/health:ignore",null,1],["compareConfigs","post","/v1/configs/compare",null,1],["suggestMatches","post","/v1/configs/compare/suggestions",null,1],["confirmLineage","post","/v1/configs/lineage",null,1],["breakLineage","delete","/v1/configs/lineage",[["type","q",true],["source_id","q",true]]],["createSyncJob","post","/v1/configs/sync-jobs",null,1],["listSyncJobs","get","/v1/configs/sync-jobs",[["CursorParam"],["SizeParam"],["status","q"]]],["getSyncJob","get","/v1/configs/sync-jobs/{id}",[["SyncJobId"]]],["getSyncJobHealth","get","/v1/configs/sync-jobs/{id}/health",[["SyncJobId"]]],["runSyncJobHealth","post","/v1/configs/sync-jobs/{id}/health:run",[["SyncJobId"]]],["retrySyncJob","post","/v1/configs/sync-jobs/{id}/retry",[["SyncJobId"]],1],["planSyncJobRetry","post","/v1/configs/sync-jobs/{id}/retry-plan",[["SyncJobId"]],1],["cancelSyncJob","post","/v1/configs/sync-jobs/{id}/cancel",[["SyncJobId"]]],["listSyncJobEvents","get","/v1/configs/sync-jobs/{id}/events",[["SyncJobId"],["CursorParam"],["size","q"],["after","q"],["kind","q"]]],["listSyncJobResources","get","/v1/configs/sync-jobs/{id}/resources",[["SyncJobId"],["CursorParam"],["size","q"],["status","q"],["type","q"],["plan_action","q"],["has_conflicts","q"]]],["getSyncPlan","get","/v1/configs/sync-jobs/{id}/plan",[["SyncJobId"]]],["listSyncPlanDiffs","get","/v1/configs/sync-jobs/{id}/plan/diffs",[["SyncJobId"],["CursorParam"],["size","q"],["type","q"],["action","q"]]],["getSyncPlanDiff","get","/v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}",[["SyncJobId"],["type","p",true],["source_id","p",true]]],["applySyncPlan","post","/v1/configs/sync-jobs/{id}/plan/apply",[["SyncJobId"]],1],["replanSyncPlan","post","/v1/configs/sync-jobs/{id}/plan/replan",[["SyncJobId"]],1],["createDeleteJob","post","/v1/configs/delete-jobs",null,1],["listDeleteJobs","get","/v1/configs/delete-jobs",[["CursorParam"],["SizeParam"]]],["getDeleteJob","get","/v1/configs/delete-jobs/{id}",[["DeleteJobId"]]],["listDeleteJobResources","get","/v1/configs/delete-jobs/{id}/resources",[["DeleteJobId"],["CursorParam"],["SizeParam"]]],["rebuildIndex","post","/v1/configs/index:rebuild"],["getConfigInventory","get","/v1/configs/inventory",[["include_dependency_only","q"]]],["createPreview","post","/v1/previews",null,1],["listPreviews","get","/v1/previews"],["getPreview","get","/v1/previews/{id}",[["id","p",true]]]],"v":"3.0.3","cp":{"ConfigType":["type","p",true],"ConfigId":["id","p",true],"CursorParam":["cursor","q"],"SizeParam":["size","q"],"SyncJobId":["id","p",true],"DeleteJobId":["id","p",true]}} \ No newline at end of file diff --git a/packages/epilot-sdk-v2/src/docs/configuration-hub.json b/packages/epilot-sdk-v2/src/docs/configuration-hub.json index 96cd2f60..cb0016fe 100644 --- a/packages/epilot-sdk-v2/src/docs/configuration-hub.json +++ b/packages/epilot-sdk-v2/src/docs/configuration-hub.json @@ -1 +1 @@ -"# Configuration Hub API\n\n- **Base URL:** `https://configuration-hub.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/configuration-hub](https://docs.epilot.io/api/configuration-hub)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.configurationHub.listConfigTypes(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/configuration-hub'\n\nconst configurationHubClient = getClient()\nauthorize(configurationHubClient, () => '')\nconst { data } = await configurationHubClient.listConfigTypes(...)\n```\n\n## Operations\n\n**Configs**\n- [`listConfigTypes`](#listconfigtypes)\n- [`listConfigs`](#listconfigs)\n- [`getConfigDependencies`](#getconfigdependencies)\n- [`getConfigUsedBy`](#getconfigusedby)\n- [`getIndex`](#getindex)\n- [`rebuildIndex`](#rebuildindex)\n- [`getConfigInventory`](#getconfiginventory)\n\n**Health**\n- [`startHealthScan`](#starthealthscan)\n- [`getHealthReport`](#gethealthreport)\n- [`requestHealthCoverage`](#requesthealthcoverage)\n- [`startHealthCleanup`](#starthealthcleanup)\n- [`setHealthFindingsIgnored`](#sethealthfindingsignored)\n\n**Compare**\n- [`compareConfigs`](#compareconfigs)\n- [`suggestMatches`](#suggestmatches)\n- [`confirmLineage`](#confirmlineage)\n- [`breakLineage`](#breaklineage)\n\n**Sync**\n- [`createSyncJob`](#createsyncjob)\n- [`listSyncJobs`](#listsyncjobs)\n- [`getSyncJob`](#getsyncjob)\n- [`getSyncJobHealth`](#getsyncjobhealth)\n- [`runSyncJobHealth`](#runsyncjobhealth)\n- [`retrySyncJob`](#retrysyncjob)\n- [`planSyncJobRetry`](#plansyncjobretry)\n- [`cancelSyncJob`](#cancelsyncjob)\n- [`listSyncJobEvents`](#listsyncjobevents)\n- [`listSyncJobResources`](#listsyncjobresources)\n- [`getSyncPlan`](#getsyncplan)\n- [`listSyncPlanDiffs`](#listsyncplandiffs)\n- [`getSyncPlanDiff`](#getsyncplandiff)\n- [`applySyncPlan`](#applysyncplan)\n- [`replanSyncPlan`](#replansyncplan)\n\n**Delete**\n- [`createDeleteJob`](#createdeletejob)\n- [`listDeleteJobs`](#listdeletejobs)\n- [`getDeleteJob`](#getdeletejob)\n- [`listDeleteJobResources`](#listdeletejobresources)\n\n**Previews**\n- [`createPreview`](#createpreview)\n- [`listPreviews`](#listpreviews)\n- [`getPreview`](#getpreview)\n\n**Schemas**\n- [`ResourceType`](#resourcetype)\n- [`ConfigTypeInfo`](#configtypeinfo)\n- [`ConfigNode`](#confignode)\n- [`ConfigListResponse`](#configlistresponse)\n- [`ConfigDependenciesResponse`](#configdependenciesresponse)\n- [`IndexRebuildResponse`](#indexrebuildresponse)\n- [`HealthScanStartResponse`](#healthscanstartresponse)\n- [`HealthFinding`](#healthfinding)\n- [`HealthReportResponse`](#healthreportresponse)\n- [`IndexStatusResponse`](#indexstatusresponse)\n- [`CompareRequest`](#comparerequest)\n- [`CompareLineage`](#comparelineage)\n- [`CompareRow`](#comparerow)\n- [`CompareCounts`](#comparecounts)\n- [`CompareResponse`](#compareresponse)\n- [`SuggestMatchesRequest`](#suggestmatchesrequest)\n- [`MatchSuggestion`](#matchsuggestion)\n- [`SuggestMatchesResponse`](#suggestmatchesresponse)\n- [`ConfirmLineageRequest`](#confirmlineagerequest)\n- [`LineageEntryResponse`](#lineageentryresponse)\n- [`BreakLineageResponse`](#breaklineageresponse)\n- [`ErrorResponse`](#errorresponse)\n- [`SyncJobStatus`](#syncjobstatus)\n- [`SyncDirection`](#syncdirection)\n- [`SyncPhase`](#syncphase)\n- [`SyncJobResourceStatus`](#syncjobresourcestatus)\n- [`SyncJobRequest`](#syncjobrequest)\n- [`SyncJobResourceRef`](#syncjobresourceref)\n- [`SyncJobRetryPlanRequest`](#syncjobretryplanrequest)\n- [`SyncJobRetryPlan`](#syncjobretryplan)\n- [`SyncJobRetryErrorResponse`](#syncjobretryerrorresponse)\n- [`SyncJobRetryRequest`](#syncjobretryrequest)\n- [`SyncJobCounts`](#syncjobcounts)\n- [`SyncJobEvent`](#syncjobevent)\n- [`SyncJobEventDependency`](#syncjobeventdependency)\n- [`SyncDependencyReason`](#syncdependencyreason)\n- [`SyncRequiredBy`](#syncrequiredby)\n- [`SyncJobEventListResponse`](#syncjobeventlistresponse)\n- [`SyncJobBatch`](#syncjobbatch)\n- [`SyncJobHealthStatus`](#syncjobhealthstatus)\n- [`HealthSeverityCounts`](#healthseveritycounts)\n- [`SyncJobHealthCheck`](#syncjobhealthcheck)\n- [`SyncJobHealth`](#syncjobhealth)\n- [`SyncJob`](#syncjob)\n- [`SyncJobListResponse`](#syncjoblistresponse)\n- [`SyncJobResource`](#syncjobresource)\n- [`SyncJobResourceListResponse`](#syncjobresourcelistresponse)\n- [`SyncPlanAction`](#syncplanaction)\n- [`SyncPlanMatch`](#syncplanmatch)\n- [`SyncPlanDiffStatus`](#syncplandiffstatus)\n- [`SyncResourcePlan`](#syncresourceplan)\n- [`SyncPlanCounts`](#syncplancounts)\n- [`SyncPlan`](#syncplan)\n- [`SyncPlanChange`](#syncplanchange)\n- [`SyncPlanDiff`](#syncplandiff)\n- [`SyncPlanDiffListResponse`](#syncplandifflistresponse)\n- [`SyncPlanApplyRequest`](#syncplanapplyrequest)\n- [`SyncPlanReplanRequest`](#syncplanreplanrequest)\n- [`SyncPlanMissingDependency`](#syncplanmissingdependency)\n- [`SyncPlanApplyErrorResponse`](#syncplanapplyerrorresponse)\n- [`DeleteJobStatus`](#deletejobstatus)\n- [`DeleteJobResourceStatus`](#deletejobresourcestatus)\n- [`DeleteJobCounts`](#deletejobcounts)\n- [`DeleteJobRequest`](#deletejobrequest)\n- [`DeleteJob`](#deletejob)\n- [`DeleteJobListResponse`](#deletejoblistresponse)\n- [`DeleteJobResource`](#deletejobresource)\n- [`DeleteJobResourceListResponse`](#deletejobresourcelistresponse)\n- [`SnapshotInventoryItem`](#snapshotinventoryitem)\n- [`SnapshotInventorySkippedType`](#snapshotinventoryskippedtype)\n- [`ConfigInventoryResponse`](#configinventoryresponse)\n- [`CreatePreviewRequest`](#createpreviewrequest)\n- [`PreviewStatus`](#previewstatus)\n- [`Preview`](#preview)\n\n### `listConfigTypes`\n\nReturns the static list of available configuration types with display metadata.\nThis is a cheap call — no fan-out to downstream APIs. Returns all known types\nwith labels and icons. The frontend should\n\n`GET /v1/configs/types`\n\n```ts\nconst { data } = await client.listConfigTypes()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"type\": \"journey\",\n \"label\": \"Journeys\",\n \"icon\": \"Route\",\n \"source_api\": \"https://journey.sls.epilot.io\",\n \"sdk_client\": \"@epilot/sdk/journey\",\n \"deletable\": true\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listConfigs`\n\nList configs of a given type with pagination. Returns summary metadata only\n(not full payloads). The frontend calls this per type folder when expanding.\n\n`GET /v1/configs/{type}`\n\n```ts\nconst { data } = await client.listConfigs({\n type: 'example',\n cursor: 'example',\n size: 1,\n q: 'example',\n updated_after: 'example',\n updated_before: 'example',\n purposes: 'example',\n blueprint_ids: 'example',\n sort: 'example',\n active_only: true,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"label\": \"string\",\n \"icon\": \"string\",\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getConfigDependencies`\n\nGet configs that are referenced by the given config.\nUsed to render children when expanding a config node in the tree.\n\n`GET /v1/configs/{type}/{id}/dependencies`\n\n```ts\nconst { data } = await client.getConfigDependencies({\n type: 'example',\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getConfigUsedBy`\n\nGet configs that reference the given config (reverse dependencies).\nScans the indexed config items for references to this config's ID or aliases.\n\n`GET /v1/configs/{type}/{id}/used_by`\n\n```ts\nconst { data } = await client.getConfigUsedBy({\n type: 'example',\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getIndex`\n\nReturn the current index build state for the caller's organization.\nClients poll this to decide whether to show a \"building\" indicator\nand when to refetch data.\n\n`GET /v1/configs/index`\n\n```ts\nconst { data } = await client.getIndex()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"missing\",\n \"org_id\": \"string\",\n \"last_built_at\": \"1970-01-01T00:00:00.000Z\",\n \"total_items\": 0,\n \"build_duration_ms\": 0\n}\n```\n\n
\n\n---\n\n### `startHealthScan`\n\nStart an asynchronous org health scan — fire-and-forget, mirrors\n`rebuildIndex`. The scan walks the indexed configs, fetches each\npayload, extracts field-path-known references (journey→mapping,\nautoma\n\n`POST /v1/configs/health:scan`\n\n```ts\nconst { data } = await client.startHealthScan()\n```\n\n---\n\n### `getHealthReport`\n\nReturn the caller org's current health report: scan status, summary\ncounts by severity, new/resolved deltas vs the previous scan, and the\nfull finding list (errors first, then most recently appeared).\n\n`GET /v1/configs/health`\n\n```ts\nconst { data } = await client.getHealthReport()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"missing\",\n \"last_scanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"scan_duration_ms\": 0,\n \"scanned_configs\": 0,\n \"scanned_by_type\": {},\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n },\n \"new_since_previous\": 0,\n \"resolved_since_previous\": 0,\n \"last_cleanup_at\": \"1970-01-01T00:00:00.000Z\",\n \"cleaned_count\": 0,\n \"history\": [\n {\n \"scanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"clean\": 0,\n \"total\": 0\n }\n ],\n \"error\": \"string\",\n \"findings\": [\n {\n \"id\": \"string\",\n \"code\": \"broken_reference\",\n \"severity\": \"error\",\n \"source_type\": \"string\",\n \"source_id\": \"string\",\n \"source_title\": \"string\",\n \"message\": \"string\",\n \"message_key\": \"string\",\n \"message_params\": {},\n \"ref_type\": \"string\",\n \"ref_id\": \"string\",\n \"ref_title\": \"string\",\n \"source_link\": \"string\",\n \"ref_link\": \"string\",\n \"owner_type\": \"string\",\n \"owner_id\": \"string\",\n \"owner_title\": \"string\",\n \"owner_link\": \"string\",\n \"path\": \"string\",\n \"cleanable\": true,\n \"ignored\": true,\n \"ignored_at\": \"1970-01-01T00:00:00.000Z\",\n \"ignored_by\": \"string\",\n \"ignored_reason\": \"string\",\n \"first_seen\": \"1970-01-01T00:00:00.000Z\",\n \"last_seen\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `requestHealthCoverage`\n\nSend a request for configuration the health check does not cover yet.\nPosted to the #epilot-org-health Slack channel together with the calling\norg and user. Returns 503 when no webhook is configured f\n\n`POST /v1/configs/health:coverage-request`\n\n```ts\nconst { data } = await client.requestHealthCoverage(\n null,\n {\n message: 'string'\n },\n)\n```\n\n---\n\n### `startHealthCleanup`\n\nRemove references that are already inert: dangling `$relation` entries\non products and prices (a price selling with a deleted tax, a product\nlinking a deleted price, …), automation triggers pointed at\n\n`POST /v1/configs/health:cleanup`\n\n```ts\nconst { data } = await client.startHealthCleanup(\n null,\n {\n finding_ids: ['string']\n },\n)\n```\n\n---\n\n### `setHealthFindingsIgnored`\n\nAcknowledge findings as intentional (or undo that). An ignored\nfinding is excluded from the headline score and the default\nbreakdown, but stays visible under the Ignored filter and can be\nun-ignored a\n\n`POST /v1/configs/health:ignore`\n\n```ts\nconst { data } = await client.setHealthFindingsIgnored(\n null,\n {\n finding_ids: ['string'],\n ignored: true,\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"ok\",\n \"count\": 0\n}\n```\n\n
\n\n---\n\n### `compareConfigs`\n\nCompare the caller org's configs of a single type against another\n(source) org, side by side. Rows are paired via the lineage registry in\nboth sync directions (caller imported from source, or source i\n\n`POST /v1/configs/compare`\n\n```ts\nconst { data } = await client.compareConfigs(\n null,\n {\n source_org_id: 'string',\n source_auth_token: 'string',\n type: 'journey'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"label\": \"string\",\n \"icon\": \"string\",\n \"rows\": [\n {\n \"match_status\": \"matched\",\n \"current\": {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n },\n \"source\": {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n },\n \"lineage\": {\n \"direction\": \"pull\",\n \"last_synced_at\": \"1970-01-01T00:00:00.000Z\",\n \"last_sync_job_id\": \"string\",\n \"origin\": \"string\"\n }\n }\n ],\n \"counts\": {\n \"matched\": 0,\n \"only_current\": 0,\n \"only_source\": 0\n }\n}\n```\n\n
\n\n---\n\n### `suggestMatches`\n\nRun the sync-grade heuristic match (`lookupByHeuristic` — name / slug /\nunique key) for a batch of source-org configs that have no lineage\nentry, and return candidate counterparts in the caller's org.\n\n`POST /v1/configs/compare/suggestions`\n\n```ts\nconst { data } = await client.suggestMatches(\n null,\n {\n source_org_id: 'string',\n source_auth_token: 'string',\n type: 'journey',\n source_ids: ['string']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"suggestions\": [\n {\n \"source_id\": \"string\",\n \"candidate\": {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n }\n ]\n}\n```\n\n
\n\n---\n\n### `confirmLineage`\n\nPersist a lineage entry pairing a source-org config with a config in\nthe caller's org — used to confirm a heuristic suggestion from the\nCompare view. Writes to the caller org's lineage partition (call\n\n`POST /v1/configs/lineage`\n\n```ts\nconst { data } = await client.confirmLineage(\n null,\n {\n type: 'journey',\n source_org_id: 'string',\n source_id: 'string',\n target_id: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"target_org_id\": \"string\",\n \"source_org_id\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"last_synced_at\": \"1970-01-01T00:00:00.000Z\",\n \"origin\": \"string\"\n}\n```\n\n
\n\n---\n\n### `breakLineage`\n\nDelete a lineage entry from the caller org's partition (caller as sync\ntarget), identified by `type` + `source_id`. Used to break a wrong or\nstale match from the Compare view. Note: a future sync can \n\n`DELETE /v1/configs/lineage`\n\n```ts\nconst { data } = await client.breakLineage({\n type: 'example',\n source_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"deleted\": true\n}\n```\n\n
\n\n---\n\n### `createSyncJob`\n\nCreate a new cross-org sync job. The job is enqueued for asynchronous execution\nby the worker Lambda; the response returns the persisted job header with status\n`pending`.\n\n`POST /v1/configs/sync-jobs`\n\n```ts\nconst { data } = await client.createSyncJob(\n null,\n {\n source_org_id: 'string',\n target_org_id: 'string',\n target_auth_token: 'string',\n name: 'string',\n dry_run: false,\n include_dependencies: true,\n sync_portal_widgets: true,\n resources: [\n {\n type: 'string',\n id: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listSyncJobs`\n\nList sync jobs scoped to the caller's organization, paginated with an opaque\ncursor. Defaults to most-recent first.\n\n`GET /v1/configs/sync-jobs`\n\n```ts\nconst { data } = await client.listSyncJobs({\n cursor: 'example',\n size: 1,\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncJob`\n\nFetch a single sync job by ID. Returns the job header, counts summary,\ncurrent phase pointer, and the latest activity events. Frontend polls this\nendpoint with a ramping interval.\n\n`GET /v1/configs/sync-jobs/{id}`\n\n```ts\nconst { data } = await client.getSyncJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncJobHealth`\n\nTargeted health check of an applied (non-dry-run) sync job. When the job\nreaches its terminal status, the target org is scanned asynchronously\nfor the configs the job wrote — rows with status `created\n\n`GET /v1/configs/sync-jobs/{id}/health`\n\n```ts\nconst { data } = await client.getSyncJobHealth({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"pending\",\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"scanned\": 0,\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n },\n \"error\": \"string\",\n \"findings\": [\n {\n \"id\": \"string\",\n \"code\": \"broken_reference\",\n \"severity\": \"error\",\n \"source_type\": \"string\",\n \"source_id\": \"string\",\n \"source_title\": \"string\",\n \"message\": \"string\",\n \"message_key\": \"string\",\n \"message_params\": {},\n \"ref_type\": \"string\",\n \"ref_id\": \"string\",\n \"ref_title\": \"string\",\n \"source_link\": \"string\",\n \"ref_link\": \"string\",\n \"owner_type\": \"string\",\n \"owner_id\": \"string\",\n \"owner_title\": \"string\",\n \"owner_link\": \"string\",\n \"path\": \"string\",\n \"cleanable\": true,\n \"ignored\": true,\n \"ignored_at\": \"1970-01-01T00:00:00.000Z\",\n \"ignored_by\": \"string\",\n \"ignored_reason\": \"string\",\n \"first_seen\": \"1970-01-01T00:00:00.000Z\",\n \"last_seen\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `runSyncJobHealth`\n\nStart the targeted health check of a finished, applied sync job again:\nfor jobs that finished before checks existed (`not_run`), checks that\nfailed or got stuck, or to re-check after fixing something.\n\n`POST /v1/configs/sync-jobs/{id}/health:run`\n\n```ts\nconst { data } = await client.runSyncJobHealth({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n---\n\n### `retrySyncJob`\n\nRetry the unresolved resources from a prior sync job: `failed` rows, plus\nrows the original run left at `pending`/`in_progress` because it stopped\nearly. Creates a new job whose scope is that `(type, \n\n`POST /v1/configs/sync-jobs/{id}/retry`\n\n```ts\nconst { data } = await client.retrySyncJob(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n payload_overrides: {}\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `planSyncJobRetry`\n\nPreview what `retrySyncJob` would re-run for the same `resources`,\nwithout creating a job: the selected rows and the unresolved rows they\ndepend on. Read-only; needs no target token.\n\n`POST /v1/configs/sync-jobs/{id}/retry-plan`\n\n```ts\nconst { data } = await client.planSyncJobRetry(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"selected\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\",\n \"reason\": \"typed_reference\"\n },\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changed_paths\": [\"string\"],\n \"note\": \"string\",\n \"depends_on\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\"\n }\n ]\n },\n \"excluded_from_apply\": \"unchanged\"\n }\n ],\n \"added_dependencies\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\",\n \"reason\": \"typed_reference\"\n },\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changed_paths\": [\"string\"],\n \"note\": \"string\",\n \"depends_on\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\"\n }\n ]\n },\n \"excluded_from_apply\": \"unchanged\"\n }\n ],\n \"dependency_resolution\": \"exact\"\n}\n```\n\n
\n\n---\n\n### `cancelSyncJob`\n\nCancel a running sync job. Marks the job `cancelled` with a `finished_at`\nso it stops being reported as in-flight, and the worker stops at its next\nbatch boundary — phases re-read the job status and a\n\n`POST /v1/configs/sync-jobs/{id}/cancel`\n\n```ts\nconst { data } = await client.cancelSyncJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listSyncJobEvents`\n\nFull activity log of a sync job, latest first: one event per resource\nstatus change. Paginate older events with `next_cursor`, or poll for\nnew ones with `after` (the newest event id the client holds).\n\n`GET /v1/configs/sync-jobs/{id}/events`\n\n```ts\nconst { data } = await client.listSyncJobEvents({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n after: 'example',\n kind: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"next_cursor\": \"string\",\n \"total\": 0,\n \"truncated\": true,\n \"source\": \"events\"\n}\n```\n\n
\n\n---\n\n### `listSyncJobResources`\n\nList the per-resource rows for a sync job. Supports filtering by status\n(e.g. `failed`) and type, and cursor pagination. Used by the job detail\npage, the failures table and the dry-run plan view in th\n\n`GET /v1/configs/sync-jobs/{id}/resources`\n\n```ts\nconst { data } = await client.listSyncJobResources({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n status: 'example',\n type: 'example',\n plan_action: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\",\n \"reason\": \"typed_reference\"\n },\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changed_paths\": [\"string\"],\n \"note\": \"string\",\n \"depends_on\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\"\n }\n ]\n },\n \"excluded_from_apply\": \"unchanged\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncPlan`\n\nSummary of a plan: a dry-run sync job (`createSyncJob` with\n`dry_run: true`). Says how many resources a real run would create,\nupdate or leave unchanged, overall and per type.\n\n`GET /v1/configs/sync-jobs/{id}/plan`\n\n```ts\nconst { data } = await client.getSyncPlan({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"job_id\": \"string\",\n \"status\": \"pending\",\n \"ready\": true,\n \"applicable\": true,\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"replanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"by_type\": [\n {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"type\": \"string\"\n }\n ],\n \"applied_by\": [\"string\"]\n}\n```\n\n
\n\n---\n\n### `listSyncPlanDiffs`\n\nField-level diffs of a plan, one per planned resource: what the target\norg has now (`before`) and what a real run would write (`after`). Use\nit to review a plan in bulk, e.g. from an AI agent.\n\n`GET /v1/configs/sync-jobs/{id}/plan/diffs`\n\n```ts\nconst { data } = await client.listSyncPlanDiffs({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n type: 'example',\n action: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changes\": [\n {\n \"path\": \"string\",\n \"kind\": \"added\",\n \"before\": {},\n \"after\": {}\n }\n ],\n \"truncated\": true,\n \"note\": \"string\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncPlanDiff`\n\nField-level diff of one planned resource. See `listSyncPlanDiffs`.\n\n`GET /v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}`\n\n```ts\nconst { data } = await client.getSyncPlanDiff({\n id: '123e4567-e89b-12d3-a456-426614174000',\n type: 'example',\n source_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changes\": [\n {\n \"path\": \"string\",\n \"kind\": \"added\",\n \"before\": {},\n \"after\": {}\n }\n ],\n \"truncated\": true,\n \"note\": \"string\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `applySyncPlan`\n\nRun a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\n`POST /v1/configs/sync-jobs/{id}/plan/apply`\n\n```ts\nconst { data } = await client.applySyncPlan(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n name: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n exclude: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n allow_missing_dependencies: false\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `replanSyncPlan`\n\nPlan some rows of a plan again, in place, e.g. after fixing a\nresource in the source org because its plan showed an unexpected\nchange. The listed resources are fetched from the source again,\ntheir dep\n\n`POST /v1/configs/sync-jobs/{id}/plan/replan`\n\n```ts\nconst { data } = await client.replanSyncPlan(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ]\n },\n)\n```\n\n---\n\n### `createDeleteJob`\n\nCreate a bulk-delete job for the caller's organization. The selected\nresources are deleted asynchronously by a worker Lambda; the response\nreturns the persisted job header with status `pending`.\n\n`POST /v1/configs/delete-jobs`\n\n```ts\nconst { data } = await client.createDeleteJob(\n null,\n {\n name: 'string',\n resources: [\n {\n type: 'string',\n id: 'string',\n title: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n}\n```\n\n
\n\n---\n\n### `listDeleteJobs`\n\nList bulk-delete jobs scoped to the caller's organization, paginated\nwith an opaque cursor. Most-recent first.\n\n`GET /v1/configs/delete-jobs`\n\n```ts\nconst { data } = await client.listDeleteJobs({\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getDeleteJob`\n\nFetch a single bulk-delete job by ID. Returns the job header and counts\nsummary. Frontend polls this endpoint while the job runs.\n\n`GET /v1/configs/delete-jobs/{id}`\n\n```ts\nconst { data } = await client.getDeleteJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n}\n```\n\n
\n\n---\n\n### `listDeleteJobResources`\n\nList the per-resource rows for a delete job, cursor-paginated. Used by\nthe failures view in the frontend.\n\n`GET /v1/configs/delete-jobs/{id}/resources`\n\n```ts\nconst { data } = await client.listDeleteJobResources({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `rebuildIndex`\n\nRebuild the configuration index for the caller's organization.\nFire-and-forget: invokes the async worker and returns immediately.\nA new rebuild will cancel any in-flight build (see `build_token`).\n\n`POST /v1/configs/index:rebuild`\n\n```ts\nconst { data } = await client.rebuildIndex()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"ready\",\n \"last_built_at\": \"1970-01-01T00:00:00.000Z\",\n \"total_items\": 0,\n \"build_duration_ms\": 0,\n \"failed_types\": [\"string\"]\n}\n```\n\n
\n\n---\n\n### `getConfigInventory`\n\nReturns a fresh inventory of an org's configuration resources — `{ type, id }` identities only,\nno full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index).\nIntended for \n\n`GET /v1/configs/inventory`\n\n```ts\nconst { data } = await client.getConfigInventory({\n include_dependency_only: true,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"inventory_id\": \"string\",\n \"org_id\": \"string\",\n \"indexed_at\": \"1970-01-01T00:00:00.000Z\",\n \"resources_count\": 0,\n \"resources\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\"\n }\n ],\n \"skipped_types\": [\n {\n \"type\": \"string\",\n \"reason\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `createPreview`\n\nStarts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`,\nplus `requester`): a new, temporary org with\n`organization_use: Preview` (parent = this org) that receives a \n\n`POST /v1/previews`\n\n```ts\nconst { data } = await client.createPreview(\n null,\n {\n name: 'string',\n requester: {\n user_id: 'string',\n email: 'user@example.com',\n display_name: 'string',\n language: 'de'\n }\n },\n)\n```\n\n---\n\n### `listPreviews`\n\nLists the previews requested from the caller's current org, newest first (max 50).\n\n`GET /v1/previews`\n\n```ts\nconst { data } = await client.listPreviews()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"creating_org\",\n \"source_org_id\": \"string\",\n \"preview_org_id\": \"string\",\n \"sync_job_id\": \"string\",\n \"sync_status\": \"string\",\n \"requested_by\": {\n \"user_id\": \"string\",\n \"email\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getPreview`\n\nReturns a preview requested from the caller's current org, with its progress.\n\n`GET /v1/previews/{id}`\n\n```ts\nconst { data } = await client.getPreview({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"creating_org\",\n \"source_org_id\": \"string\",\n \"preview_org_id\": \"string\",\n \"sync_job_id\": \"string\",\n \"sync_status\": \"string\",\n \"requested_by\": {\n \"user_id\": \"string\",\n \"email\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"error\": \"string\"\n}\n```\n\n
\n\n---\n\n## Schemas\n\n### `ResourceType`\n\nConfiguration resource type identifier.\nMatches blueprint-manifest-api V3 naming conventions.\n\n\n```ts\ntype ResourceType = \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n```\n\n### `ConfigTypeInfo`\n\nStatic metadata for a config type folder in the tree.\nNo downstream API calls — just type + label + icon + source API info.\n\n\n```ts\ntype ConfigTypeInfo = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n source_api: string\n sdk_client: string\n deletable: boolean\n}\n```\n\n### `ConfigNode`\n\nSummary metadata for a single configuration item in the tree\n\n```ts\ntype ConfigNode = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: string\n title: string\n }>\n metadata?: Record\n}\n```\n\n### `ConfigListResponse`\n\nCursor-paginated list of configs for a specific type\n\n```ts\ntype ConfigListResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n total?: number\n next_cursor?: string\n results: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }>\n}\n```\n\n### `ConfigDependenciesResponse`\n\nCursor-paginated list of configs referenced by a given config\n\n```ts\ntype ConfigDependenciesResponse = {\n total?: number\n next_cursor?: string\n results: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }>\n}\n```\n\n### `IndexRebuildResponse`\n\nResult of an index rebuild operation\n\n```ts\ntype IndexRebuildResponse = {\n status: \"ready\" | \"building\" | \"failed\" | \"already_building\"\n last_built_at?: string // date-time\n total_items?: number\n build_duration_ms?: number\n failed_types?: string[]\n}\n```\n\n### `HealthScanStartResponse`\n\n```ts\ntype HealthScanStartResponse = {\n status: \"scanning\" | \"failed\"\n error?: string\n}\n```\n\n### `HealthFinding`\n\n```ts\ntype HealthFinding = {\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n}\n```\n\n### `HealthReportResponse`\n\n```ts\ntype HealthReportResponse = {\n status: \"missing\" | \"idle\" | \"scanning\" | \"cleaning\" | \"ready\" | \"failed\"\n last_scanned_at?: string // date-time\n scan_duration_ms?: number\n scanned_configs?: number\n scanned_by_type?: Record\n counts?: {\n error?: number\n warning?: number\n info?: number\n }\n new_since_previous?: number\n resolved_since_previous?: number\n last_cleanup_at?: string // date-time\n cleaned_count?: number\n history?: Array<{\n scanned_at: string // date-time\n clean: number\n total: number\n }>\n error?: string\n findings: Array<{\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n }>\n}\n```\n\n### `IndexStatusResponse`\n\nCurrent index build state\n\n```ts\ntype IndexStatusResponse = {\n status: \"missing\" | \"building\" | \"ready\" | \"failed\"\n org_id?: string\n last_built_at?: string // date-time\n total_items?: number\n build_duration_ms?: number\n}\n```\n\n### `CompareRequest`\n\nRequest body for a per-type cross-org comparison\n\n```ts\ntype CompareRequest = {\n source_org_id: string\n source_auth_token: string\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n}\n```\n\n### `CompareLineage`\n\nProvenance of a lineage-backed match\n\n```ts\ntype CompareLineage = {\n direction: \"pull\" | \"push\"\n last_synced_at?: string // date-time\n last_sync_job_id?: string\n origin?: string\n}\n```\n\n### `CompareRow`\n\nOne row of the side-by-side comparison. `matched` rows carry both\nconfigs plus lineage provenance; one-sided rows carry only the side\nthe config exists on.\n\n\n```ts\ntype CompareRow = {\n match_status: \"matched\" | \"only_current\" | \"only_source\"\n current?: {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }\n source?: {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }\n lineage?: {\n direction: \"pull\" | \"push\"\n last_synced_at?: string // date-time\n last_sync_job_id?: string\n origin?: string\n }\n}\n```\n\n### `CompareCounts`\n\nRow counts per match status\n\n```ts\ntype CompareCounts = {\n matched: number\n only_current: number\n only_source: number\n}\n```\n\n### `CompareResponse`\n\nSide-by-side comparison of one config type across two orgs\n\n```ts\ntype CompareResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n rows: Array<{\n match_status: \"matched\" | \"only_current\" | \"only_source\"\n current?: {\n type: { ... }\n id: { ... }\n title: { ... }\n updated_at?: { ... }\n updated_by?: { ... }\n tags?: { ... }\n aliases?: { ... }\n purposes?: { ... }\n link?: { ... }\n active?: { ... }\n blueprints?: { ... }\n metadata?: { ... }\n }\n source?: {\n type: { ... }\n id: { ... }\n title: { ... }\n updated_at?: { ... }\n updated_by?: { ... }\n tags?: { ... }\n aliases?: { ... }\n purposes?: { ... }\n link?: { ... }\n active?: { ... }\n blueprints?: { ... }\n metadata?: { ... }\n }\n lineage?: {\n direction: { ... }\n last_synced_at?: { ... }\n last_sync_job_id?: { ... }\n origin?: { ... }\n }\n }>\n counts: {\n matched: number\n only_current: number\n only_source: number\n }\n}\n```\n\n### `SuggestMatchesRequest`\n\nRequest body for batched heuristic match suggestions\n\n```ts\ntype SuggestMatchesRequest = {\n source_org_id: string\n source_auth_token: string\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n source_ids: string[]\n}\n```\n\n### `MatchSuggestion`\n\nHeuristic candidate for one source config (null candidate = no match)\n\n```ts\ntype MatchSuggestion = {\n source_id: string\n candidate?: {\n id: string\n title?: string\n }\n}\n```\n\n### `SuggestMatchesResponse`\n\n```ts\ntype SuggestMatchesResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n suggestions: Array<{\n source_id: string\n candidate?: {\n id: { ... }\n title?: { ... }\n }\n }>\n}\n```\n\n### `ConfirmLineageRequest`\n\nPersist a source→target lineage pairing (caller org as target)\n\n```ts\ntype ConfirmLineageRequest = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n source_org_id: string\n source_id: string\n target_id: string\n}\n```\n\n### `LineageEntryResponse`\n\nA persisted lineage entry\n\n```ts\ntype LineageEntryResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n target_org_id: string\n source_org_id: string\n source_id: string\n target_id: string\n last_synced_at?: string // date-time\n origin?: string\n}\n```\n\n### `BreakLineageResponse`\n\n```ts\ntype BreakLineageResponse = {\n deleted: boolean\n}\n```\n\n### `ErrorResponse`\n\n```ts\ntype ErrorResponse = {\n status: number\n code?: string\n error: string\n}\n```\n\n### `SyncJobStatus`\n\nLifecycle status of a sync job. See `docs/sync/INTERFACES.md` for state\ntransitions.\n\n\n```ts\ntype SyncJobStatus = \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n```\n\n### `SyncDirection`\n\nDirection of the sync, derived from the source/target pane selection in the\nconfiguration hub UI.\n\n\n```ts\ntype SyncDirection = \"push\" | \"pull\"\n```\n\n### `SyncPhase`\n\nThree-phase orchestrator phase. `phase_0` fetches source payloads,\n`dependencies` expands the selection with its dependencies (only when\n`include_dependencies` is on), `phase_a` creates/matches with\ntopological batches, `phase_a5` resolves derived references, `phase_b`\npatches with the full ID map, \n\n```ts\ntype SyncPhase = \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n```\n\n### `SyncJobResourceStatus`\n\nPer-resource status. `would_*` values are produced by dry-run jobs.\n\n\n```ts\ntype SyncJobResourceStatus = \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n```\n\n### `SyncJobRequest`\n\nRequest body for `createSyncJob`. `target_auth_token` is the destination\norg's auth token and MUST NOT be persisted or logged — it is `writeOnly`.\n\n\n```ts\ntype SyncJobRequest = {\n source_org_id: string\n target_org_id: string\n target_auth_token: string\n name?: string\n dry_run?: boolean\n include_dependencies?: boolean\n sync_portal_widgets?: boolean\n resources: Array<{\n type: string\n id: string\n }>\n}\n```\n\n### `SyncJobResourceRef`\n\n```ts\ntype SyncJobResourceRef = {\n type: string\n source_id: string\n}\n```\n\n### `SyncJobRetryPlanRequest`\n\n```ts\ntype SyncJobRetryPlanRequest = {\n resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncJobRetryPlan`\n\n```ts\ntype SyncJobRetryPlan = {\n selected: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n added_dependencies: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n dependency_resolution: \"exact\" | \"best_effort\"\n}\n```\n\n### `SyncJobRetryErrorResponse`\n\n```ts\ntype SyncJobRetryErrorResponse = {\n status?: number\n error?: string\n invalid_resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncJobRetryRequest`\n\nOptional body for `retrySyncJob`. Defaults to retrying every unresolved\nresource of the original job (failed, pending or in_progress).\n\n\n```ts\ntype SyncJobRetryRequest = {\n target_auth_token?: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n payload_overrides?: Record\n}\n```\n\n### `SyncJobCounts`\n\nAggregate counters by resource status.\n\n```ts\ntype SyncJobCounts = {\n total: number\n pending: number\n in_progress: number\n succeeded: number\n failed: number\n skipped_unchanged: number\n}\n```\n\n### `SyncJobEvent`\n\nActivity-log entry surfaced to the frontend. Backed by the append-only\njob event log, or by the op-log rows (`SYNC#``#OP#```) for jobs\nthat predate it. Key events by `id` when present.\n\n\n```ts\ntype SyncJobEvent = {\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: \"added\" | \"linked\" | \"skipped\" | \"not_found\"\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n required_by: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n }\n depth: number\n }\n}\n```\n\n### `SyncJobEventDependency`\n\nOnly on `kind: dependency` events. The event's `type`, `source_id` and\n`title` describe the dependency itself.\n\n\n```ts\ntype SyncJobEventDependency = {\n outcome: \"added\" | \"linked\" | \"skipped\" | \"not_found\"\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n required_by: {\n type: string\n source_id: string\n title?: string\n }\n depth: number\n}\n```\n\n### `SyncDependencyReason`\n\nHow the dependency was found. `typed_reference`: a structural reference\nin the payload (e.g. a workflow step's automation). `id_reference`: an\nid in the payload resolved through the source index.\n`automation_trigger`: an automation triggered by the resource (e.g. a\njourney submission). `journey_data\n\n```ts\ntype SyncDependencyReason = \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n```\n\n### `SyncRequiredBy`\n\nThe resource that referenced the dependency.\n\n```ts\ntype SyncRequiredBy = {\n type: string\n source_id: string\n title?: string\n}\n```\n\n### `SyncJobEventListResponse`\n\n```ts\ntype SyncJobEventListResponse = {\n results: Array<{\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: { ... }\n reason: { ... }\n required_by: { ... }\n depth: { ... }\n }\n }>\n next_cursor?: string\n total?: number\n truncated?: boolean\n source: \"events\" | \"op_log\"\n}\n```\n\n### `SyncJobBatch`\n\nPosition within the current topological batch for the active phase.\n\n```ts\ntype SyncJobBatch = {\n index: number\n of: number\n level: number\n}\n```\n\n### `SyncJobHealthStatus`\n\nState of a sync job's post-import health check. `pending`: queued,\n`running`: scanning, `done`: findings available, `failed`: the check\nitself failed or did not finish (the import is unaffected), `skipped`:\nnothing to check (a plan, a job that wrote nothing, or a cancelled\njob), `not_run`: a finishe\n\n```ts\ntype SyncJobHealthStatus = \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n```\n\n### `HealthSeverityCounts`\n\n```ts\ntype HealthSeverityCounts = {\n error: number\n warning: number\n info: number\n}\n```\n\n### `SyncJobHealthCheck`\n\nCompact state of the job's post-import health check. Full results:\n`getSyncJobHealth`. Absent on plans and on jobs that finished before\nthe check existed.\n\n\n```ts\ntype SyncJobHealthCheck = {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n counts: {\n error: number\n warning: number\n info: number\n }\n}\n```\n\n### `SyncJobHealth`\n\nTargeted health check of the configs a sync job wrote into the target\norg. Findings carry `first_seen` = `last_seen` = `finished_at`.\n\n\n```ts\ntype SyncJobHealth = {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n started_at?: string // date-time\n finished_at?: string // date-time\n scanned: number\n counts: {\n error: number\n warning: number\n info: number\n }\n error?: string\n findings: Array<{\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n }>\n}\n```\n\n### `SyncJob`\n\nSync job header as surfaced by `getSyncJob` and the create response. The\ncanonical persistence shape is described in `docs/sync/INTERFACES.md`.\n\n\n```ts\ntype SyncJob = {\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n direction: \"push\" | \"pull\"\n source_org_id: string\n target_org_id: string\n dry_run: boolean\n counts: {\n total: number\n pending: number\n in_progress: number\n succeeded: number\n failed: number\n skipped_unchanged: number\n }\n current_phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n current_batch?: {\n index: number\n of: number\n level: number\n }\n started_at: string // date-time\n finished_at?: string // date-time\n health_check?: {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n counts: {\n error: { ... }\n warning: { ... }\n info: { ... }\n }\n }\n events?: Array<{\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: { ... }\n reason: { ... }\n required_by: { ... }\n depth: { ... }\n }\n }>\n created_by?: string\n retry_of?: string\n retried_by?: string[]\n plan_only?: boolean\n plan_summary?: {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n }\n planned_at?: string // date-time\n expires_at?: string // date-time\n applied_from_plan?: string\n applied_by?: string[]\n approved_by?: string\n approved_at?: string // date-time\n approvals?: Array<{\n job_id: string\n approved_by?: string\n approved_at: string // date-time\n }>\n errors_sample?: Array<{\n type: string\n source_id: string\n error: string\n }>\n}\n```\n\n### `SyncJobListResponse`\n\nCursor-paginated list of sync jobs.\n\n```ts\ntype SyncJobListResponse = {\n next_cursor?: string\n results: Array<{\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n direction: \"push\" | \"pull\"\n source_org_id: string\n target_org_id: string\n dry_run: boolean\n counts: {\n total: { ... }\n pending: { ... }\n in_progress: { ... }\n succeeded: { ... }\n failed: { ... }\n skipped_unchanged: { ... }\n }\n current_phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n current_batch?: {\n index: { ... }\n of: { ... }\n level: { ... }\n }\n started_at: string // date-time\n finished_at?: string // date-time\n health_check?: {\n status: { ... }\n counts: { ... }\n }\n events?: Array<{\n id?: { ... }\n kind?: { ... }\n seq: { ... }\n ts: { ... }\n phase?: { ... }\n type?: { ... }\n source_id?: { ... }\n target_id?: { ... }\n title?: { ... }\n status: { ... }\n attempt?: { ... }\n message?: { ... }\n error?: { ... }\n dependency?: { ... }\n }>\n created_by?: string\n retry_of?: string\n retried_by?: string[]\n plan_only?: boolean\n plan_summary?: {\n total: { ... }\n create: { ... }\n update: { ... }\n no_change: { ... }\n skip: { ... }\n failed: { ... }\n not_compared: { ... }\n }\n planned_at?: string // date-time\n expires_at?: string // date-time\n applied_from_plan?: string\n applied_by?: string[]\n approved_by?: string\n approved_at?: string // date-time\n approvals?: Array<{\n job_id: { ... }\n approved_by?: { ... }\n approved_at: { ... }\n }>\n errors_sample?: Array<{\n type: { ... }\n source_id: { ... }\n error: { ... }\n }>\n }>\n}\n```\n\n### `SyncJobResource`\n\nPer-resource row backed by `SyncResourcesTable`. See\n`docs/sync/INTERFACES.md` for the DDB shape.\n\n\n```ts\ntype SyncJobResource = {\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: string\n source_id: string\n title?: string\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status?: \"computed\" | \"not_compared\"\n change_count?: number\n changed_paths?: string[]\n note?: string\n depends_on?: Array<{\n type: { ... }\n source_id: { ... }\n }>\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n}\n```\n\n### `SyncJobResourceListResponse`\n\nCursor-paginated list of sync job resources.\n\n```ts\ntype SyncJobResourceListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n}\n```\n\n### `SyncPlanAction`\n\nWhat a real run would do with a planned resource:\n- `create`: no counterpart in the target; a new resource is created.\n- `update`: the target's copy differs from the source and is\n overwritten. Also used when the copies could not be compared\n (`diff_status: not_compared`): such resources are re-ap\n\n```ts\ntype SyncPlanAction = \"create\" | \"update\" | \"no_change\" | \"skip\"\n```\n\n### `SyncPlanMatch`\n\nHow the target counterpart was found: `lineage` (synced before),\n`heuristic` (first sync, matched by name / slug / unique key) or\n`none` (no counterpart: the resource is created).\n\n\n```ts\ntype SyncPlanMatch = \"lineage\" | \"heuristic\" | \"none\"\n```\n\n### `SyncPlanDiffStatus`\n\n`computed` when both copies were read and compared. `not_compared`\nwhen the target could not be read or compared (see `note`).\n\n\n```ts\ntype SyncPlanDiffStatus = \"computed\" | \"not_compared\"\n```\n\n### `SyncResourcePlan`\n\nThe plan for one resource. Set on rows of plan jobs (`dry_run`), and\ncopied onto the rows of the job that applies the plan. Absent on rows\nplanning failed for and on jobs created before plans existed.\n\n\n```ts\ntype SyncResourcePlan = {\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status?: \"computed\" | \"not_compared\"\n change_count?: number\n changed_paths?: string[]\n note?: string\n depends_on?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncPlanCounts`\n\nPlanned resources by action. `not_compared` counts the `update`\nresources whose copies could not be compared (it is part of\n`update`, not in addition to it). `failed` counts resources planning\nfailed for.\n\n\n```ts\ntype SyncPlanCounts = {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n}\n```\n\n### `SyncPlan`\n\n```ts\ntype SyncPlan = {\n job_id: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n ready: boolean\n applicable: boolean\n expires_at?: string // date-time\n planned_at?: string // date-time\n replanned_at?: string // date-time\n source_org_id: string\n target_org_id: string\n started_at?: string // date-time\n finished_at?: string // date-time\n summary: {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n }\n by_type: Array<{\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n type: string\n }>\n applied_by?: string[]\n}\n```\n\n### `SyncPlanChange`\n\nOne changed field. `path` uses dots for object keys and\n`[key=value]` for array items that have an identity (`name`, `slug`,\n`key`, `id`, …), e.g. `steps[name=Contact].blocks[name=Email].label`.\n`$` is the whole resource.\n\nValues are compared after removing fields that differ between orgs\nby design:\n\n```ts\ntype SyncPlanChange = {\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n before?: unknown\n after?: unknown\n}\n```\n\n### `SyncPlanDiff`\n\n```ts\ntype SyncPlanDiff = {\n type: string\n source_id: string\n target_id?: string\n title?: string\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status: \"computed\" | \"not_compared\"\n change_count: number\n changes: Array<{\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n before?: unknown\n after?: unknown\n }>\n truncated: boolean\n note?: string\n planned_at?: string // date-time\n}\n```\n\n### `SyncPlanDiffListResponse`\n\n```ts\ntype SyncPlanDiffListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status: \"computed\" | \"not_compared\"\n change_count: number\n changes: Array<{\n path: { ... }\n kind: { ... }\n before?: { ... }\n after?: { ... }\n }>\n truncated: boolean\n note?: string\n planned_at?: string // date-time\n }>\n}\n```\n\n### `SyncPlanApplyRequest`\n\n```ts\ntype SyncPlanApplyRequest = {\n target_auth_token: string\n name?: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n exclude?: Array<{\n type: string\n source_id: string\n }>\n allow_missing_dependencies?: boolean\n}\n```\n\n### `SyncPlanReplanRequest`\n\n```ts\ntype SyncPlanReplanRequest = {\n target_auth_token: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncPlanMissingDependency`\n\n```ts\ntype SyncPlanMissingDependency = {\n type: string\n source_id: string\n title?: string\n required_by: Array<{\n type: string\n source_id: string\n title?: string\n }>\n}\n```\n\n### `SyncPlanApplyErrorResponse`\n\n```ts\ntype SyncPlanApplyErrorResponse = {\n status?: number\n error?: string\n code?: \"target_auth_token_missing\" | \"empty_selection\" | \"invalid_resources\" | \"plan_not_ready\" | \"plan_expired\" | \"nothing_to_apply\" | \"missing_dependencies\"\n invalid_resources?: Array<{\n type: string\n source_id: string\n }>\n missing_dependencies?: Array<{\n type: string\n source_id: string\n title?: string\n required_by: Array<{\n type: { ... }\n source_id: { ... }\n title?: { ... }\n }>\n }>\n}\n```\n\n### `DeleteJobStatus`\n\nLifecycle status of a bulk-delete job.\n\n```ts\ntype DeleteJobStatus = \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n```\n\n### `DeleteJobResourceStatus`\n\nPer-resource delete status. `skipped` is used for protected resources\nthat are intentionally not deleted (e.g. built-in schemas).\n\n\n```ts\ntype DeleteJobResourceStatus = \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n```\n\n### `DeleteJobCounts`\n\nAggregate counters by resource delete status.\n\n```ts\ntype DeleteJobCounts = {\n total: number\n pending: number\n in_progress: number\n deleted: number\n skipped: number\n failed: number\n}\n```\n\n### `DeleteJobRequest`\n\nRequest body for `createDeleteJob`.\n\n```ts\ntype DeleteJobRequest = {\n name?: string\n resources: Array<{\n type: string\n id: string\n title?: string\n }>\n}\n```\n\n### `DeleteJob`\n\nBulk-delete job header surfaced by `createDeleteJob`/`getDeleteJob`.\n\n```ts\ntype DeleteJob = {\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n org_id: string\n counts: {\n total: number\n pending: number\n in_progress: number\n deleted: number\n skipped: number\n failed: number\n }\n started_at: string // date-time\n finished_at?: string // date-time\n rebuild_triggered?: boolean\n}\n```\n\n### `DeleteJobListResponse`\n\nCursor-paginated list of delete jobs.\n\n```ts\ntype DeleteJobListResponse = {\n next_cursor?: string\n results: Array<{\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n org_id: string\n counts: {\n total: { ... }\n pending: { ... }\n in_progress: { ... }\n deleted: { ... }\n skipped: { ... }\n failed: { ... }\n }\n started_at: string // date-time\n finished_at?: string // date-time\n rebuild_triggered?: boolean\n }>\n}\n```\n\n### `DeleteJobResource`\n\nPer-resource row for a delete job.\n\n```ts\ntype DeleteJobResource = {\n type: string\n id: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n attempt: number\n error?: string\n updated_at: string // date-time\n}\n```\n\n### `DeleteJobResourceListResponse`\n\nCursor-paginated list of delete job resources.\n\n```ts\ntype DeleteJobResourceListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n id: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n attempt: number\n error?: string\n updated_at: string // date-time\n }>\n}\n```\n\n### `SnapshotInventoryItem`\n\nMinimal identity of a single configuration resource.\n\n```ts\ntype SnapshotInventoryItem = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n}\n```\n\n### `SnapshotInventorySkippedType`\n\nA type that could not be listed during inventory collection.\n\n```ts\ntype SnapshotInventorySkippedType = {\n type: string\n reason: string\n}\n```\n\n### `ConfigInventoryResponse`\n\nFresh inventory of all configuration resources for an org.\n\n```ts\ntype ConfigInventoryResponse = {\n inventory_id: string\n org_id: string\n indexed_at: string // date-time\n resources_count: number\n resources: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n }>\n skipped_types: Array<{\n type: string\n reason: string\n }>\n}\n```\n\n### `CreatePreviewRequest`\n\n```ts\ntype CreatePreviewRequest = {\n name?: string\n requester?: {\n user_id?: string\n email?: string // email\n display_name?: string\n language?: \"de\" | \"en\"\n }\n}\n```\n\n### `PreviewStatus`\n\n- creating_org: the preview org is being created\n- copying_configuration: the sync job copying this org's configuration is running\n- inviting: the requester is being invited to the preview org\n- ready: the requester got the \"your preview org is ready\" email\n- failed: see `error`\n\n\n```ts\ntype PreviewStatus = \"creating_org\" | \"copying_configuration\" | \"inviting\" | \"ready\" | \"failed\"\n```\n\n### `Preview`\n\n```ts\ntype Preview = {\n id: string\n name: string\n status: \"creating_org\" | \"copying_configuration\" | \"inviting\" | \"ready\" | \"failed\"\n source_org_id: string\n preview_org_id?: string\n sync_job_id?: string\n sync_status?: string\n requested_by: {\n user_id?: string\n email?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n error?: string\n}\n```\n" +"# Configuration Hub API\n\n- **Base URL:** `https://configuration-hub.sls.epilot.io`\n- **Full API Docs:** [https://docs.epilot.io/api/configuration-hub](https://docs.epilot.io/api/configuration-hub)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.configurationHub.listConfigTypes(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/configuration-hub'\n\nconst configurationHubClient = getClient()\nauthorize(configurationHubClient, () => '')\nconst { data } = await configurationHubClient.listConfigTypes(...)\n```\n\n## Operations\n\n**Configs**\n- [`listConfigTypes`](#listconfigtypes)\n- [`listConfigs`](#listconfigs)\n- [`getConfigDependencies`](#getconfigdependencies)\n- [`getConfigUsedBy`](#getconfigusedby)\n- [`getIndex`](#getindex)\n- [`rebuildIndex`](#rebuildindex)\n- [`getConfigInventory`](#getconfiginventory)\n\n**Health**\n- [`startHealthScan`](#starthealthscan)\n- [`getHealthReport`](#gethealthreport)\n- [`requestHealthCoverage`](#requesthealthcoverage)\n- [`startHealthCleanup`](#starthealthcleanup)\n- [`setHealthFindingsIgnored`](#sethealthfindingsignored)\n\n**Compare**\n- [`compareConfigs`](#compareconfigs)\n- [`suggestMatches`](#suggestmatches)\n- [`confirmLineage`](#confirmlineage)\n- [`breakLineage`](#breaklineage)\n\n**Sync**\n- [`createSyncJob`](#createsyncjob)\n- [`listSyncJobs`](#listsyncjobs)\n- [`getSyncJob`](#getsyncjob)\n- [`getSyncJobHealth`](#getsyncjobhealth)\n- [`runSyncJobHealth`](#runsyncjobhealth)\n- [`retrySyncJob`](#retrysyncjob)\n- [`planSyncJobRetry`](#plansyncjobretry)\n- [`cancelSyncJob`](#cancelsyncjob)\n- [`listSyncJobEvents`](#listsyncjobevents)\n- [`listSyncJobResources`](#listsyncjobresources)\n- [`getSyncPlan`](#getsyncplan)\n- [`listSyncPlanDiffs`](#listsyncplandiffs)\n- [`getSyncPlanDiff`](#getsyncplandiff)\n- [`applySyncPlan`](#applysyncplan)\n- [`replanSyncPlan`](#replansyncplan)\n\n**Delete**\n- [`createDeleteJob`](#createdeletejob)\n- [`listDeleteJobs`](#listdeletejobs)\n- [`getDeleteJob`](#getdeletejob)\n- [`listDeleteJobResources`](#listdeletejobresources)\n\n**Previews**\n- [`createPreview`](#createpreview)\n- [`listPreviews`](#listpreviews)\n- [`getPreview`](#getpreview)\n\n**Schemas**\n- [`ResourceType`](#resourcetype)\n- [`ConfigTypeInfo`](#configtypeinfo)\n- [`ConfigNode`](#confignode)\n- [`ConfigListResponse`](#configlistresponse)\n- [`ConfigDependenciesResponse`](#configdependenciesresponse)\n- [`IndexRebuildResponse`](#indexrebuildresponse)\n- [`HealthScanStartResponse`](#healthscanstartresponse)\n- [`HealthFinding`](#healthfinding)\n- [`HealthReportResponse`](#healthreportresponse)\n- [`IndexStatusResponse`](#indexstatusresponse)\n- [`CompareRequest`](#comparerequest)\n- [`CompareLineage`](#comparelineage)\n- [`CompareRow`](#comparerow)\n- [`CompareCounts`](#comparecounts)\n- [`CompareResponse`](#compareresponse)\n- [`SuggestMatchesRequest`](#suggestmatchesrequest)\n- [`MatchSuggestion`](#matchsuggestion)\n- [`SuggestMatchesResponse`](#suggestmatchesresponse)\n- [`ConfirmLineageRequest`](#confirmlineagerequest)\n- [`LineageEntryResponse`](#lineageentryresponse)\n- [`BreakLineageResponse`](#breaklineageresponse)\n- [`ErrorResponse`](#errorresponse)\n- [`SyncJobStatus`](#syncjobstatus)\n- [`SyncDirection`](#syncdirection)\n- [`SyncPhase`](#syncphase)\n- [`SyncJobResourceStatus`](#syncjobresourcestatus)\n- [`SyncJobRequest`](#syncjobrequest)\n- [`SyncJobResourceRef`](#syncjobresourceref)\n- [`SyncJobRetryPlanRequest`](#syncjobretryplanrequest)\n- [`SyncJobRetryPlan`](#syncjobretryplan)\n- [`SyncJobRetryErrorResponse`](#syncjobretryerrorresponse)\n- [`SyncJobRetryRequest`](#syncjobretryrequest)\n- [`SyncJobCounts`](#syncjobcounts)\n- [`SyncJobEvent`](#syncjobevent)\n- [`SyncJobEventDependency`](#syncjobeventdependency)\n- [`SyncDependencyReason`](#syncdependencyreason)\n- [`SyncRequiredBy`](#syncrequiredby)\n- [`SyncJobEventListResponse`](#syncjobeventlistresponse)\n- [`SyncJobBatch`](#syncjobbatch)\n- [`SyncJobHealthStatus`](#syncjobhealthstatus)\n- [`HealthSeverityCounts`](#healthseveritycounts)\n- [`SyncJobHealthCheck`](#syncjobhealthcheck)\n- [`SyncJobHealth`](#syncjobhealth)\n- [`SyncJob`](#syncjob)\n- [`SyncJobListResponse`](#syncjoblistresponse)\n- [`SyncJobResource`](#syncjobresource)\n- [`SyncJobResourceListResponse`](#syncjobresourcelistresponse)\n- [`SyncPlanAction`](#syncplanaction)\n- [`SyncPlanMatch`](#syncplanmatch)\n- [`SyncPlanDiffStatus`](#syncplandiffstatus)\n- [`SyncResourcePlan`](#syncresourceplan)\n- [`SyncPlanConflict`](#syncplanconflict)\n- [`SyncPlanConflictPath`](#syncplanconflictpath)\n- [`SyncPlanCounts`](#syncplancounts)\n- [`SyncPlan`](#syncplan)\n- [`SyncPlanChange`](#syncplanchange)\n- [`SyncPlanDiff`](#syncplandiff)\n- [`SyncPlanDiffListResponse`](#syncplandifflistresponse)\n- [`SyncPlanApplyRequest`](#syncplanapplyrequest)\n- [`SyncPlanReplanRequest`](#syncplanreplanrequest)\n- [`SyncPlanMissingDependency`](#syncplanmissingdependency)\n- [`SyncPlanApplyErrorResponse`](#syncplanapplyerrorresponse)\n- [`SyncPlanUnresolvedConflict`](#syncplanunresolvedconflict)\n- [`DeleteJobStatus`](#deletejobstatus)\n- [`DeleteJobResourceStatus`](#deletejobresourcestatus)\n- [`DeleteJobCounts`](#deletejobcounts)\n- [`DeleteJobRequest`](#deletejobrequest)\n- [`DeleteJob`](#deletejob)\n- [`DeleteJobListResponse`](#deletejoblistresponse)\n- [`DeleteJobResource`](#deletejobresource)\n- [`DeleteJobResourceListResponse`](#deletejobresourcelistresponse)\n- [`SnapshotInventoryItem`](#snapshotinventoryitem)\n- [`SnapshotInventorySkippedType`](#snapshotinventoryskippedtype)\n- [`ConfigInventoryResponse`](#configinventoryresponse)\n- [`CreatePreviewRequest`](#createpreviewrequest)\n- [`PreviewStatus`](#previewstatus)\n- [`Preview`](#preview)\n\n### `listConfigTypes`\n\nReturns the static list of available configuration types with display metadata.\nThis is a cheap call — no fan-out to downstream APIs. Returns all known types\nwith labels and icons. The frontend should\n\n`GET /v1/configs/types`\n\n```ts\nconst { data } = await client.listConfigTypes()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"type\": \"journey\",\n \"label\": \"Journeys\",\n \"icon\": \"Route\",\n \"source_api\": \"https://journey.sls.epilot.io\",\n \"sdk_client\": \"@epilot/sdk/journey\",\n \"deletable\": true\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listConfigs`\n\nList configs of a given type with pagination. Returns summary metadata only\n(not full payloads). The frontend calls this per type folder when expanding.\n\n`GET /v1/configs/{type}`\n\n```ts\nconst { data } = await client.listConfigs({\n type: 'example',\n cursor: 'example',\n size: 1,\n q: 'example',\n updated_after: 'example',\n updated_before: 'example',\n purposes: 'example',\n blueprint_ids: 'example',\n sort: 'example',\n active_only: true,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"label\": \"string\",\n \"icon\": \"string\",\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getConfigDependencies`\n\nGet configs that are referenced by the given config.\nUsed to render children when expanding a config node in the tree.\n\n`GET /v1/configs/{type}/{id}/dependencies`\n\n```ts\nconst { data } = await client.getConfigDependencies({\n type: 'example',\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getConfigUsedBy`\n\nGet configs that reference the given config (reverse dependencies).\nScans the indexed config items for references to this config's ID or aliases.\n\n`GET /v1/configs/{type}/{id}/used_by`\n\n```ts\nconst { data } = await client.getConfigUsedBy({\n type: 'example',\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"total\": 0,\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getIndex`\n\nReturn the current index build state for the caller's organization.\nClients poll this to decide whether to show a \"building\" indicator\nand when to refetch data.\n\n`GET /v1/configs/index`\n\n```ts\nconst { data } = await client.getIndex()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"missing\",\n \"org_id\": \"string\",\n \"last_built_at\": \"1970-01-01T00:00:00.000Z\",\n \"total_items\": 0,\n \"build_duration_ms\": 0\n}\n```\n\n
\n\n---\n\n### `startHealthScan`\n\nStart an asynchronous org health scan — fire-and-forget, mirrors\n`rebuildIndex`. The scan walks the indexed configs, fetches each\npayload, extracts field-path-known references (journey→mapping,\nautoma\n\n`POST /v1/configs/health:scan`\n\n```ts\nconst { data } = await client.startHealthScan()\n```\n\n---\n\n### `getHealthReport`\n\nReturn the caller org's current health report: scan status, summary\ncounts by severity, new/resolved deltas vs the previous scan, and the\nfull finding list (errors first, then most recently appeared).\n\n`GET /v1/configs/health`\n\n```ts\nconst { data } = await client.getHealthReport()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"missing\",\n \"last_scanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"scan_duration_ms\": 0,\n \"scanned_configs\": 0,\n \"scanned_by_type\": {},\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n },\n \"new_since_previous\": 0,\n \"resolved_since_previous\": 0,\n \"last_cleanup_at\": \"1970-01-01T00:00:00.000Z\",\n \"cleaned_count\": 0,\n \"history\": [\n {\n \"scanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"clean\": 0,\n \"total\": 0\n }\n ],\n \"error\": \"string\",\n \"findings\": [\n {\n \"id\": \"string\",\n \"code\": \"broken_reference\",\n \"severity\": \"error\",\n \"source_type\": \"string\",\n \"source_id\": \"string\",\n \"source_title\": \"string\",\n \"message\": \"string\",\n \"message_key\": \"string\",\n \"message_params\": {},\n \"ref_type\": \"string\",\n \"ref_id\": \"string\",\n \"ref_title\": \"string\",\n \"source_link\": \"string\",\n \"ref_link\": \"string\",\n \"owner_type\": \"string\",\n \"owner_id\": \"string\",\n \"owner_title\": \"string\",\n \"owner_link\": \"string\",\n \"path\": \"string\",\n \"cleanable\": true,\n \"ignored\": true,\n \"ignored_at\": \"1970-01-01T00:00:00.000Z\",\n \"ignored_by\": \"string\",\n \"ignored_reason\": \"string\",\n \"first_seen\": \"1970-01-01T00:00:00.000Z\",\n \"last_seen\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `requestHealthCoverage`\n\nSend a request for configuration the health check does not cover yet.\nPosted to the #epilot-org-health Slack channel together with the calling\norg and user. Returns 503 when no webhook is configured f\n\n`POST /v1/configs/health:coverage-request`\n\n```ts\nconst { data } = await client.requestHealthCoverage(\n null,\n {\n message: 'string'\n },\n)\n```\n\n---\n\n### `startHealthCleanup`\n\nRemove references that are already inert: dangling `$relation` entries\non products and prices (a price selling with a deleted tax, a product\nlinking a deleted price, …), automation triggers pointed at\n\n`POST /v1/configs/health:cleanup`\n\n```ts\nconst { data } = await client.startHealthCleanup(\n null,\n {\n finding_ids: ['string']\n },\n)\n```\n\n---\n\n### `setHealthFindingsIgnored`\n\nAcknowledge findings as intentional (or undo that). An ignored\nfinding is excluded from the headline score and the default\nbreakdown, but stays visible under the Ignored filter and can be\nun-ignored a\n\n`POST /v1/configs/health:ignore`\n\n```ts\nconst { data } = await client.setHealthFindingsIgnored(\n null,\n {\n finding_ids: ['string'],\n ignored: true,\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"ok\",\n \"count\": 0\n}\n```\n\n
\n\n---\n\n### `compareConfigs`\n\nCompare the caller org's configs of a single type against another\n(source) org, side by side. Rows are paired via the lineage registry in\nboth sync directions (caller imported from source, or source i\n\n`POST /v1/configs/compare`\n\n```ts\nconst { data } = await client.compareConfigs(\n null,\n {\n source_org_id: 'string',\n source_auth_token: 'string',\n type: 'journey'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"label\": \"string\",\n \"icon\": \"string\",\n \"rows\": [\n {\n \"match_status\": \"matched\",\n \"current\": {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n },\n \"source\": {\n \"type\": \"journey\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_by\": \"string\",\n \"tags\": [\"string\"],\n \"aliases\": [\"string\"],\n \"purposes\": [\"string\"],\n \"link\": \"https://example.com/path\",\n \"active\": true,\n \"blueprints\": [\n {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n ],\n \"metadata\": {}\n },\n \"lineage\": {\n \"direction\": \"pull\",\n \"last_synced_at\": \"1970-01-01T00:00:00.000Z\",\n \"last_sync_job_id\": \"string\",\n \"origin\": \"string\"\n }\n }\n ],\n \"counts\": {\n \"matched\": 0,\n \"only_current\": 0,\n \"only_source\": 0\n }\n}\n```\n\n
\n\n---\n\n### `suggestMatches`\n\nRun the sync-grade heuristic match (`lookupByHeuristic` — name / slug /\nunique key) for a batch of source-org configs that have no lineage\nentry, and return candidate counterparts in the caller's org.\n\n`POST /v1/configs/compare/suggestions`\n\n```ts\nconst { data } = await client.suggestMatches(\n null,\n {\n source_org_id: 'string',\n source_auth_token: 'string',\n type: 'journey',\n source_ids: ['string']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"suggestions\": [\n {\n \"source_id\": \"string\",\n \"candidate\": {\n \"id\": \"string\",\n \"title\": \"string\"\n }\n }\n ]\n}\n```\n\n
\n\n---\n\n### `confirmLineage`\n\nPersist a lineage entry pairing a source-org config with a config in\nthe caller's org — used to confirm a heuristic suggestion from the\nCompare view. Writes to the caller org's lineage partition (call\n\n`POST /v1/configs/lineage`\n\n```ts\nconst { data } = await client.confirmLineage(\n null,\n {\n type: 'journey',\n source_org_id: 'string',\n source_id: 'string',\n target_id: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"journey\",\n \"target_org_id\": \"string\",\n \"source_org_id\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"last_synced_at\": \"1970-01-01T00:00:00.000Z\",\n \"origin\": \"string\"\n}\n```\n\n
\n\n---\n\n### `breakLineage`\n\nDelete a lineage entry from the caller org's partition (caller as sync\ntarget), identified by `type` + `source_id`. Used to break a wrong or\nstale match from the Compare view. Note: a future sync can \n\n`DELETE /v1/configs/lineage`\n\n```ts\nconst { data } = await client.breakLineage({\n type: 'example',\n source_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"deleted\": true\n}\n```\n\n
\n\n---\n\n### `createSyncJob`\n\nCreate a new cross-org sync job. The job is enqueued for asynchronous execution\nby the worker Lambda; the response returns the persisted job header with status\n`pending`.\n\n`POST /v1/configs/sync-jobs`\n\n```ts\nconst { data } = await client.createSyncJob(\n null,\n {\n source_org_id: 'string',\n target_org_id: 'string',\n target_auth_token: 'string',\n name: 'string',\n dry_run: false,\n include_dependencies: true,\n sync_portal_widgets: true,\n resources: [\n {\n type: 'string',\n id: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listSyncJobs`\n\nList sync jobs scoped to the caller's organization, paginated with an opaque\ncursor. Defaults to most-recent first.\n\n`GET /v1/configs/sync-jobs`\n\n```ts\nconst { data } = await client.listSyncJobs({\n cursor: 'example',\n size: 1,\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncJob`\n\nFetch a single sync job by ID. Returns the job header, counts summary,\ncurrent phase pointer, and the latest activity events. Frontend polls this\nendpoint with a ramping interval.\n\n`GET /v1/configs/sync-jobs/{id}`\n\n```ts\nconst { data } = await client.getSyncJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncJobHealth`\n\nTargeted health check of an applied (non-dry-run) sync job. When the job\nreaches its terminal status, the target org is scanned asynchronously\nfor the configs the job wrote — rows with status `created\n\n`GET /v1/configs/sync-jobs/{id}/health`\n\n```ts\nconst { data } = await client.getSyncJobHealth({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"pending\",\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"scanned\": 0,\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n },\n \"error\": \"string\",\n \"findings\": [\n {\n \"id\": \"string\",\n \"code\": \"broken_reference\",\n \"severity\": \"error\",\n \"source_type\": \"string\",\n \"source_id\": \"string\",\n \"source_title\": \"string\",\n \"message\": \"string\",\n \"message_key\": \"string\",\n \"message_params\": {},\n \"ref_type\": \"string\",\n \"ref_id\": \"string\",\n \"ref_title\": \"string\",\n \"source_link\": \"string\",\n \"ref_link\": \"string\",\n \"owner_type\": \"string\",\n \"owner_id\": \"string\",\n \"owner_title\": \"string\",\n \"owner_link\": \"string\",\n \"path\": \"string\",\n \"cleanable\": true,\n \"ignored\": true,\n \"ignored_at\": \"1970-01-01T00:00:00.000Z\",\n \"ignored_by\": \"string\",\n \"ignored_reason\": \"string\",\n \"first_seen\": \"1970-01-01T00:00:00.000Z\",\n \"last_seen\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `runSyncJobHealth`\n\nStart the targeted health check of a finished, applied sync job again:\nfor jobs that finished before checks existed (`not_run`), checks that\nfailed or got stuck, or to re-check after fixing something.\n\n`POST /v1/configs/sync-jobs/{id}/health:run`\n\n```ts\nconst { data } = await client.runSyncJobHealth({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n---\n\n### `retrySyncJob`\n\nRetry the unresolved resources from a prior sync job: `failed` rows, plus\nrows the original run left at `pending`/`in_progress` because it stopped\nearly. Creates a new job whose scope is that `(type, \n\n`POST /v1/configs/sync-jobs/{id}/retry`\n\n```ts\nconst { data } = await client.retrySyncJob(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n payload_overrides: {}\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `planSyncJobRetry`\n\nPreview what `retrySyncJob` would re-run for the same `resources`,\nwithout creating a job: the selected rows and the unresolved rows they\ndepend on. Read-only; needs no target token.\n\n`POST /v1/configs/sync-jobs/{id}/retry-plan`\n\n```ts\nconst { data } = await client.planSyncJobRetry(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"selected\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {},\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {},\n \"excluded_from_apply\": \"unchanged\"\n }\n ],\n \"added_dependencies\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {},\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {},\n \"excluded_from_apply\": \"unchanged\"\n }\n ],\n \"dependency_resolution\": \"exact\"\n}\n```\n\n
\n\n---\n\n### `cancelSyncJob`\n\nCancel a running sync job. Marks the job `cancelled` with a `finished_at`\nso it stops being reported as in-flight, and the worker stops at its next\nbatch boundary — phases re-read the job status and a\n\n`POST /v1/configs/sync-jobs/{id}/cancel`\n\n```ts\nconst { data } = await client.cancelSyncJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `listSyncJobEvents`\n\nFull activity log of a sync job, latest first: one event per resource\nstatus change. Paginate older events with `next_cursor`, or poll for\nnew ones with `after` (the newest event id the client holds).\n\n`GET /v1/configs/sync-jobs/{id}/events`\n\n```ts\nconst { data } = await client.listSyncJobEvents({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n after: 'example',\n kind: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"next_cursor\": \"string\",\n \"total\": 0,\n \"truncated\": true,\n \"source\": \"events\"\n}\n```\n\n
\n\n---\n\n### `listSyncJobResources`\n\nList the per-resource rows for a sync job. Supports filtering by status\n(e.g. `failed`) and type, and cursor pagination. Used by the job detail\npage, the failures table and the dry-run plan view in th\n\n`GET /v1/configs/sync-jobs/{id}/resources`\n\n```ts\nconst { data } = await client.listSyncJobResources({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n status: 'example',\n type: 'example',\n plan_action: 'example',\n has_conflicts: true,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\",\n \"reason\": \"typed_reference\"\n },\n \"status\": \"pending\",\n \"phase\": \"phase_0\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_link\": \"string\",\n \"target_link\": \"string\",\n \"plan\": {\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changed_paths\": [\"string\"],\n \"note\": \"string\",\n \"depends_on\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\"\n }\n ],\n \"conflicts\": [\n {\n \"code\": \"mostly_replaced\",\n \"certainty\": \"exact\",\n \"requires_decision\": true,\n \"paths\": [\n {\n \"path\": \"string\",\n \"kind\": \"added\",\n \"base\": {},\n \"source\": {},\n \"target\": {}\n }\n ],\n \"paths_truncated\": true,\n \"total_paths\": 0,\n \"last_synced_at\": \"1970-01-01T00:00:00.000Z\",\n \"target_updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"target_updated_by\": \"string\",\n \"target_updated_by_name\": \"string\",\n \"source_updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"replaced_ratio\": 0.92,\n \"replaced_fields\": 198,\n \"total_fields\": 214\n }\n ],\n \"conflict_resolution\": \"overwrite\"\n },\n \"excluded_from_apply\": \"unchanged\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncPlan`\n\nSummary of a plan: a dry-run sync job (`createSyncJob` with\n`dry_run: true`). Says how many resources a real run would create,\nupdate or leave unchanged, overall and per type.\n\n`GET /v1/configs/sync-jobs/{id}/plan`\n\n```ts\nconst { data } = await client.getSyncPlan({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"job_id\": \"string\",\n \"status\": \"pending\",\n \"ready\": true,\n \"applicable\": true,\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"replanned_at\": \"1970-01-01T00:00:00.000Z\",\n \"conflicts_checked_at\": \"1970-01-01T00:00:00.000Z\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"by_type\": [\n {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0,\n \"type\": \"string\"\n }\n ],\n \"applied_by\": [\"string\"]\n}\n```\n\n
\n\n---\n\n### `listSyncPlanDiffs`\n\nField-level diffs of a plan, one per planned resource: what the target\norg has now (`before`) and what a real run would write (`after`). Use\nit to review a plan in bulk, e.g. from an AI agent.\n\n`GET /v1/configs/sync-jobs/{id}/plan/diffs`\n\n```ts\nconst { data } = await client.listSyncPlanDiffs({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n type: 'example',\n action: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changes\": [\n {\n \"path\": \"string\",\n \"kind\": \"added\",\n \"before\": {},\n \"after\": {}\n }\n ],\n \"truncated\": true,\n \"note\": \"string\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getSyncPlanDiff`\n\nField-level diff of one planned resource. See `listSyncPlanDiffs`.\n\n`GET /v1/configs/sync-jobs/{id}/plan/diffs/{type}/{source_id}`\n\n```ts\nconst { data } = await client.getSyncPlanDiff({\n id: '123e4567-e89b-12d3-a456-426614174000',\n type: 'example',\n source_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"action\": \"create\",\n \"match\": \"lineage\",\n \"diff_status\": \"computed\",\n \"change_count\": 0,\n \"changes\": [\n {\n \"path\": \"string\",\n \"kind\": \"added\",\n \"before\": {},\n \"after\": {}\n }\n ],\n \"truncated\": true,\n \"note\": \"string\",\n \"planned_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `applySyncPlan`\n\nRun a finished plan for real. Creates a new sync job (it carries\n`applied_from_plan`; the plan's `applied_by` gets the new job's id)\nand enqueues it.\n\n`POST /v1/configs/sync-jobs/{id}/plan/apply`\n\n```ts\nconst { data } = await client.applySyncPlan(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n name: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n exclude: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n overwrite_conflicts: [\n {\n type: 'string',\n source_id: 'string'\n }\n ],\n allow_missing_dependencies: false\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"direction\": \"push\",\n \"source_org_id\": \"string\",\n \"target_org_id\": \"string\",\n \"dry_run\": true,\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"succeeded\": 0,\n \"failed\": 0,\n \"skipped_unchanged\": 0\n },\n \"current_phase\": \"phase_0\",\n \"current_batch\": {\n \"index\": 0,\n \"of\": 0,\n \"level\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"health_check\": {\n \"status\": \"pending\",\n \"counts\": {\n \"error\": 0,\n \"warning\": 0,\n \"info\": 0\n }\n },\n \"events\": [\n {\n \"id\": \"string\",\n \"kind\": \"resource\",\n \"seq\": 0,\n \"ts\": \"1970-01-01T00:00:00.000Z\",\n \"phase\": \"phase_0\",\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"target_id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"message\": \"string\",\n \"error\": \"string\",\n \"dependency\": {\n \"outcome\": \"added\",\n \"reason\": \"typed_reference\",\n \"required_by\": {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"title\": \"string\"\n },\n \"depth\": 0\n }\n }\n ],\n \"created_by\": \"string\",\n \"retry_of\": \"string\",\n \"retried_by\": [\"string\"],\n \"plan_only\": true,\n \"plan_summary\": {\n \"total\": 0,\n \"create\": 0,\n \"update\": 0,\n \"no_change\": 0,\n \"skip\": 0,\n \"failed\": 0,\n \"not_compared\": 0,\n \"conflicts\": 0\n },\n \"planned_at\": \"1970-01-01T00:00:00.000Z\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"applied_from_plan\": \"string\",\n \"applied_by\": [\"string\"],\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"approvals\": [\n {\n \"job_id\": \"string\",\n \"approved_by\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ],\n \"errors_sample\": [\n {\n \"type\": \"string\",\n \"source_id\": \"string\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `replanSyncPlan`\n\nPlan some rows of a plan again, in place, e.g. after fixing a\nresource in the source org because its plan showed an unexpected\nchange. The listed resources are fetched from the source again,\ntheir dep\n\n`POST /v1/configs/sync-jobs/{id}/plan/replan`\n\n```ts\nconst { data } = await client.replanSyncPlan(\n {\n id: '123e4567-e89b-12d3-a456-426614174000',\n },\n {\n target_auth_token: 'string',\n resources: [\n {\n type: 'string',\n source_id: 'string'\n }\n ]\n },\n)\n```\n\n---\n\n### `createDeleteJob`\n\nCreate a bulk-delete job for the caller's organization. The selected\nresources are deleted asynchronously by a worker Lambda; the response\nreturns the persisted job header with status `pending`.\n\n`POST /v1/configs/delete-jobs`\n\n```ts\nconst { data } = await client.createDeleteJob(\n null,\n {\n name: 'string',\n resources: [\n {\n type: 'string',\n id: 'string',\n title: 'string'\n }\n ]\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n}\n```\n\n
\n\n---\n\n### `listDeleteJobs`\n\nList bulk-delete jobs scoped to the caller's organization, paginated\nwith an opaque cursor. Most-recent first.\n\n`GET /v1/configs/delete-jobs`\n\n```ts\nconst { data } = await client.listDeleteJobs({\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getDeleteJob`\n\nFetch a single bulk-delete job by ID. Returns the job header and counts\nsummary. Frontend polls this endpoint while the job runs.\n\n`GET /v1/configs/delete-jobs/{id}`\n\n```ts\nconst { data } = await client.getDeleteJob({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"pending\",\n \"org_id\": \"string\",\n \"counts\": {\n \"total\": 0,\n \"pending\": 0,\n \"in_progress\": 0,\n \"deleted\": 0,\n \"skipped\": 0,\n \"failed\": 0\n },\n \"started_at\": \"1970-01-01T00:00:00.000Z\",\n \"finished_at\": \"1970-01-01T00:00:00.000Z\",\n \"rebuild_triggered\": true\n}\n```\n\n
\n\n---\n\n### `listDeleteJobResources`\n\nList the per-resource rows for a delete job, cursor-paginated. Used by\nthe failures view in the frontend.\n\n`GET /v1/configs/delete-jobs/{id}/resources`\n\n```ts\nconst { data } = await client.listDeleteJobResources({\n id: '123e4567-e89b-12d3-a456-426614174000',\n cursor: 'example',\n size: 1,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"next_cursor\": \"string\",\n \"results\": [\n {\n \"type\": \"string\",\n \"id\": \"string\",\n \"title\": \"string\",\n \"status\": \"pending\",\n \"attempt\": 0,\n \"error\": \"string\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `rebuildIndex`\n\nRebuild the configuration index for the caller's organization.\nFire-and-forget: invokes the async worker and returns immediately.\nA new rebuild will cancel any in-flight build (see `build_token`).\n\n`POST /v1/configs/index:rebuild`\n\n```ts\nconst { data } = await client.rebuildIndex()\n```\n\n
\nResponse\n\n```json\n{\n \"status\": \"ready\",\n \"last_built_at\": \"1970-01-01T00:00:00.000Z\",\n \"total_items\": 0,\n \"build_duration_ms\": 0,\n \"failed_types\": [\"string\"]\n}\n```\n\n
\n\n---\n\n### `getConfigInventory`\n\nReturns a fresh inventory of an org's configuration resources — `{ type, id }` identities only,\nno full payloads. Calls every adapter's `list()` live (bypasses the 7-day DynamoDB index).\nIntended for \n\n`GET /v1/configs/inventory`\n\n```ts\nconst { data } = await client.getConfigInventory({\n include_dependency_only: true,\n})\n```\n\n
\nResponse\n\n```json\n{\n \"inventory_id\": \"string\",\n \"org_id\": \"string\",\n \"indexed_at\": \"1970-01-01T00:00:00.000Z\",\n \"resources_count\": 0,\n \"resources\": [\n {\n \"type\": \"journey\",\n \"id\": \"string\"\n }\n ],\n \"skipped_types\": [\n {\n \"type\": \"string\",\n \"reason\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `createPreview`\n\nStarts a preview of the caller's current org (internal callers: the org in `x-ivy-org-id`,\nplus `requester`): a new, temporary org with\n`organization_use: Preview` (parent = this org) that receives a \n\n`POST /v1/previews`\n\n```ts\nconst { data } = await client.createPreview(\n null,\n {\n name: 'string',\n requester: {\n user_id: 'string',\n email: 'user@example.com',\n display_name: 'string',\n language: 'de'\n }\n },\n)\n```\n\n---\n\n### `listPreviews`\n\nLists the previews requested from the caller's current org, newest first (max 50).\n\n`GET /v1/previews`\n\n```ts\nconst { data } = await client.listPreviews()\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"creating_org\",\n \"source_org_id\": \"string\",\n \"preview_org_id\": \"string\",\n \"sync_job_id\": \"string\",\n \"sync_status\": \"string\",\n \"skipped_types\": [\n {\n \"type\": \"string\",\n \"reason\": \"string\"\n }\n ],\n \"requested_by\": {\n \"user_id\": \"string\",\n \"email\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"error\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `getPreview`\n\nReturns a preview requested from the caller's current org, with its progress.\n\n`GET /v1/previews/{id}`\n\n```ts\nconst { data } = await client.getPreview({\n id: '123e4567-e89b-12d3-a456-426614174000',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"id\": \"string\",\n \"name\": \"string\",\n \"status\": \"creating_org\",\n \"source_org_id\": \"string\",\n \"preview_org_id\": \"string\",\n \"sync_job_id\": \"string\",\n \"sync_status\": \"string\",\n \"skipped_types\": [\n {\n \"type\": \"string\",\n \"reason\": \"string\"\n }\n ],\n \"requested_by\": {\n \"user_id\": \"string\",\n \"email\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"error\": \"string\"\n}\n```\n\n
\n\n---\n\n## Schemas\n\n### `ResourceType`\n\nConfiguration resource type identifier.\nMatches blueprint-manifest-api V3 naming conventions.\n\n\n```ts\ntype ResourceType = \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n```\n\n### `ConfigTypeInfo`\n\nStatic metadata for a config type folder in the tree.\nNo downstream API calls — just type + label + icon + source API info.\n\n\n```ts\ntype ConfigTypeInfo = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n source_api: string\n sdk_client: string\n deletable: boolean\n}\n```\n\n### `ConfigNode`\n\nSummary metadata for a single configuration item in the tree\n\n```ts\ntype ConfigNode = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: string\n title: string\n }>\n metadata?: Record\n}\n```\n\n### `ConfigListResponse`\n\nCursor-paginated list of configs for a specific type\n\n```ts\ntype ConfigListResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n total?: number\n next_cursor?: string\n results: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }>\n}\n```\n\n### `ConfigDependenciesResponse`\n\nCursor-paginated list of configs referenced by a given config\n\n```ts\ntype ConfigDependenciesResponse = {\n total?: number\n next_cursor?: string\n results: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }>\n}\n```\n\n### `IndexRebuildResponse`\n\nResult of an index rebuild operation\n\n```ts\ntype IndexRebuildResponse = {\n status: \"ready\" | \"building\" | \"failed\" | \"already_building\"\n last_built_at?: string // date-time\n total_items?: number\n build_duration_ms?: number\n failed_types?: string[]\n}\n```\n\n### `HealthScanStartResponse`\n\n```ts\ntype HealthScanStartResponse = {\n status: \"scanning\" | \"failed\"\n error?: string\n}\n```\n\n### `HealthFinding`\n\n```ts\ntype HealthFinding = {\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n}\n```\n\n### `HealthReportResponse`\n\n```ts\ntype HealthReportResponse = {\n status: \"missing\" | \"idle\" | \"scanning\" | \"cleaning\" | \"ready\" | \"failed\"\n last_scanned_at?: string // date-time\n scan_duration_ms?: number\n scanned_configs?: number\n scanned_by_type?: Record\n counts?: {\n error?: number\n warning?: number\n info?: number\n }\n new_since_previous?: number\n resolved_since_previous?: number\n last_cleanup_at?: string // date-time\n cleaned_count?: number\n history?: Array<{\n scanned_at: string // date-time\n clean: number\n total: number\n }>\n error?: string\n findings: Array<{\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n }>\n}\n```\n\n### `IndexStatusResponse`\n\nCurrent index build state\n\n```ts\ntype IndexStatusResponse = {\n status: \"missing\" | \"building\" | \"ready\" | \"failed\"\n org_id?: string\n last_built_at?: string // date-time\n total_items?: number\n build_duration_ms?: number\n}\n```\n\n### `CompareRequest`\n\nRequest body for a per-type cross-org comparison\n\n```ts\ntype CompareRequest = {\n source_org_id: string\n source_auth_token: string\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n}\n```\n\n### `CompareLineage`\n\nProvenance of a lineage-backed match\n\n```ts\ntype CompareLineage = {\n direction: \"pull\" | \"push\"\n last_synced_at?: string // date-time\n last_sync_job_id?: string\n origin?: string\n}\n```\n\n### `CompareRow`\n\nOne row of the side-by-side comparison. `matched` rows carry both\nconfigs plus lineage provenance; one-sided rows carry only the side\nthe config exists on.\n\n\n```ts\ntype CompareRow = {\n match_status: \"matched\" | \"only_current\" | \"only_source\"\n current?: {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }\n source?: {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n title: string\n updated_at?: string // date-time\n updated_by?: string\n tags?: string[]\n aliases?: string[]\n purposes?: string[]\n link?: string // uri\n active?: boolean\n blueprints?: Array<{\n id: { ... }\n title: { ... }\n }>\n metadata?: Record\n }\n lineage?: {\n direction: \"pull\" | \"push\"\n last_synced_at?: string // date-time\n last_sync_job_id?: string\n origin?: string\n }\n}\n```\n\n### `CompareCounts`\n\nRow counts per match status\n\n```ts\ntype CompareCounts = {\n matched: number\n only_current: number\n only_source: number\n}\n```\n\n### `CompareResponse`\n\nSide-by-side comparison of one config type across two orgs\n\n```ts\ntype CompareResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n label: string\n icon: string\n rows: Array<{\n match_status: \"matched\" | \"only_current\" | \"only_source\"\n current?: {\n type: { ... }\n id: { ... }\n title: { ... }\n updated_at?: { ... }\n updated_by?: { ... }\n tags?: { ... }\n aliases?: { ... }\n purposes?: { ... }\n link?: { ... }\n active?: { ... }\n blueprints?: { ... }\n metadata?: { ... }\n }\n source?: {\n type: { ... }\n id: { ... }\n title: { ... }\n updated_at?: { ... }\n updated_by?: { ... }\n tags?: { ... }\n aliases?: { ... }\n purposes?: { ... }\n link?: { ... }\n active?: { ... }\n blueprints?: { ... }\n metadata?: { ... }\n }\n lineage?: {\n direction: { ... }\n last_synced_at?: { ... }\n last_sync_job_id?: { ... }\n origin?: { ... }\n }\n }>\n counts: {\n matched: number\n only_current: number\n only_source: number\n }\n}\n```\n\n### `SuggestMatchesRequest`\n\nRequest body for batched heuristic match suggestions\n\n```ts\ntype SuggestMatchesRequest = {\n source_org_id: string\n source_auth_token: string\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n source_ids: string[]\n}\n```\n\n### `MatchSuggestion`\n\nHeuristic candidate for one source config (null candidate = no match)\n\n```ts\ntype MatchSuggestion = {\n source_id: string\n candidate?: {\n id: string\n title?: string\n }\n}\n```\n\n### `SuggestMatchesResponse`\n\n```ts\ntype SuggestMatchesResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n suggestions: Array<{\n source_id: string\n candidate?: {\n id: { ... }\n title?: { ... }\n }\n }>\n}\n```\n\n### `ConfirmLineageRequest`\n\nPersist a source→target lineage pairing (caller org as target)\n\n```ts\ntype ConfirmLineageRequest = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n source_org_id: string\n source_id: string\n target_id: string\n}\n```\n\n### `LineageEntryResponse`\n\nA persisted lineage entry\n\n```ts\ntype LineageEntryResponse = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n target_org_id: string\n source_org_id: string\n source_id: string\n target_id: string\n last_synced_at?: string // date-time\n origin?: string\n}\n```\n\n### `BreakLineageResponse`\n\n```ts\ntype BreakLineageResponse = {\n deleted: boolean\n}\n```\n\n### `ErrorResponse`\n\n```ts\ntype ErrorResponse = {\n status: number\n code?: string\n error: string\n}\n```\n\n### `SyncJobStatus`\n\nLifecycle status of a sync job. See `docs/sync/INTERFACES.md` for state\ntransitions.\n\n\n```ts\ntype SyncJobStatus = \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n```\n\n### `SyncDirection`\n\nDirection of the sync, derived from the source/target pane selection in the\nconfiguration hub UI.\n\n\n```ts\ntype SyncDirection = \"push\" | \"pull\"\n```\n\n### `SyncPhase`\n\nThree-phase orchestrator phase. `phase_0` fetches source payloads,\n`dependencies` expands the selection with its dependencies (only when\n`include_dependencies` is on), `phase_a` creates/matches with\ntopological batches, `phase_a5` resolves derived references, `phase_b`\npatches with the full ID map, \n\n```ts\ntype SyncPhase = \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n```\n\n### `SyncJobResourceStatus`\n\nPer-resource status. `would_*` values are produced by dry-run jobs.\n\n\n```ts\ntype SyncJobResourceStatus = \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n```\n\n### `SyncJobRequest`\n\nRequest body for `createSyncJob`. `target_auth_token` is the destination\norg's auth token and MUST NOT be persisted or logged — it is `writeOnly`.\n\n\n```ts\ntype SyncJobRequest = {\n source_org_id: string\n target_org_id: string\n target_auth_token: string\n name?: string\n dry_run?: boolean\n include_dependencies?: boolean\n sync_portal_widgets?: boolean\n resources: Array<{\n type: string\n id: string\n }>\n}\n```\n\n### `SyncJobResourceRef`\n\n```ts\ntype SyncJobResourceRef = {\n type: string\n source_id: string\n}\n```\n\n### `SyncJobRetryPlanRequest`\n\n```ts\ntype SyncJobRetryPlanRequest = {\n resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncJobRetryPlan`\n\n```ts\ntype SyncJobRetryPlan = {\n selected: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n conflicts?: { ... }\n conflict_resolution?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n added_dependencies: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n conflicts?: { ... }\n conflict_resolution?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n dependency_resolution: \"exact\" | \"best_effort\"\n}\n```\n\n### `SyncJobRetryErrorResponse`\n\n```ts\ntype SyncJobRetryErrorResponse = {\n status?: number\n error?: string\n invalid_resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncJobRetryRequest`\n\nOptional body for `retrySyncJob`. Defaults to retrying every unresolved\nresource of the original job (failed, pending or in_progress).\n\n\n```ts\ntype SyncJobRetryRequest = {\n target_auth_token?: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n payload_overrides?: Record\n}\n```\n\n### `SyncJobCounts`\n\nAggregate counters by resource status.\n\n```ts\ntype SyncJobCounts = {\n total: number\n pending: number\n in_progress: number\n succeeded: number\n failed: number\n skipped_unchanged: number\n}\n```\n\n### `SyncJobEvent`\n\nActivity-log entry surfaced to the frontend. Backed by the append-only\njob event log, or by the op-log rows (`SYNC#``#OP#```) for jobs\nthat predate it. Key events by `id` when present.\n\n\n```ts\ntype SyncJobEvent = {\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: \"added\" | \"linked\" | \"skipped\" | \"not_found\"\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n required_by: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n }\n depth: number\n }\n}\n```\n\n### `SyncJobEventDependency`\n\nOnly on `kind: dependency` events. The event's `type`, `source_id` and\n`title` describe the dependency itself.\n\n\n```ts\ntype SyncJobEventDependency = {\n outcome: \"added\" | \"linked\" | \"skipped\" | \"not_found\"\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n required_by: {\n type: string\n source_id: string\n title?: string\n }\n depth: number\n}\n```\n\n### `SyncDependencyReason`\n\nHow the dependency was found. `typed_reference`: a structural reference\nin the payload (e.g. a workflow step's automation). `id_reference`: an\nid in the payload resolved through the source index.\n`automation_trigger`: an automation triggered by the resource (e.g. a\njourney submission). `journey_data\n\n```ts\ntype SyncDependencyReason = \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n```\n\n### `SyncRequiredBy`\n\nThe resource that referenced the dependency.\n\n```ts\ntype SyncRequiredBy = {\n type: string\n source_id: string\n title?: string\n}\n```\n\n### `SyncJobEventListResponse`\n\n```ts\ntype SyncJobEventListResponse = {\n results: Array<{\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: { ... }\n reason: { ... }\n required_by: { ... }\n depth: { ... }\n }\n }>\n next_cursor?: string\n total?: number\n truncated?: boolean\n source: \"events\" | \"op_log\"\n}\n```\n\n### `SyncJobBatch`\n\nPosition within the current topological batch for the active phase.\n\n```ts\ntype SyncJobBatch = {\n index: number\n of: number\n level: number\n}\n```\n\n### `SyncJobHealthStatus`\n\nState of a sync job's post-import health check. `pending`: queued,\n`running`: scanning, `done`: findings available, `failed`: the check\nitself failed or did not finish (the import is unaffected), `skipped`:\nnothing to check (a plan, a job that wrote nothing, or a cancelled\njob), `not_run`: a finishe\n\n```ts\ntype SyncJobHealthStatus = \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n```\n\n### `HealthSeverityCounts`\n\n```ts\ntype HealthSeverityCounts = {\n error: number\n warning: number\n info: number\n}\n```\n\n### `SyncJobHealthCheck`\n\nCompact state of the job's post-import health check. Full results:\n`getSyncJobHealth`. Absent on plans and on jobs that finished before\nthe check existed.\n\n\n```ts\ntype SyncJobHealthCheck = {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n counts: {\n error: number\n warning: number\n info: number\n }\n}\n```\n\n### `SyncJobHealth`\n\nTargeted health check of the configs a sync job wrote into the target\norg. Findings carry `first_seen` = `last_seen` = `finished_at`.\n\n\n```ts\ntype SyncJobHealth = {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n started_at?: string // date-time\n finished_at?: string // date-time\n scanned: number\n counts: {\n error: number\n warning: number\n info: number\n }\n error?: string\n findings: Array<{\n id: string\n code: \"broken_reference\" | \"inactive_reference\" | \"unreadable_config\" | \"orphaned_group_reference\" | \"uuid_group_label\" | \"duplicate_attribute_name\" | \"duplicate_headline\" | \"unreachable_attribute_purpose\" | \"orphaned_price\" | \"duplicate_price\" | \"inactive_price_in_use\" | \"incomplete_configuration\" | \"source_org_reference\" | \"secret_value_missing\" | \"env_value_missing\"\n severity: \"error\" | \"warning\" | \"info\"\n source_type: string\n source_id: string\n source_title: string\n message: string\n message_key?: string\n message_params?: Record\n ref_type?: string\n ref_id?: string\n ref_title?: string\n source_link?: string\n ref_link?: string\n owner_type?: string\n owner_id?: string\n owner_title?: string\n owner_link?: string\n path?: string\n cleanable?: boolean\n ignored?: boolean\n ignored_at?: string // date-time\n ignored_by?: string\n ignored_reason?: string\n first_seen: string // date-time\n last_seen: string // date-time\n }>\n}\n```\n\n### `SyncJob`\n\nSync job header as surfaced by `getSyncJob` and the create response. The\ncanonical persistence shape is described in `docs/sync/INTERFACES.md`.\n\n\n```ts\ntype SyncJob = {\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n direction: \"push\" | \"pull\"\n source_org_id: string\n target_org_id: string\n dry_run: boolean\n counts: {\n total: number\n pending: number\n in_progress: number\n succeeded: number\n failed: number\n skipped_unchanged: number\n }\n current_phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n current_batch?: {\n index: number\n of: number\n level: number\n }\n started_at: string // date-time\n finished_at?: string // date-time\n health_check?: {\n status: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\" | \"not_run\"\n counts: {\n error: { ... }\n warning: { ... }\n info: { ... }\n }\n }\n events?: Array<{\n id?: string\n kind?: \"resource\" | \"dependency\" | \"step\"\n seq: number\n ts: string // date-time\n phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n type?: string\n source_id?: string\n target_id?: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n attempt?: number\n message?: string\n error?: string\n dependency?: {\n outcome: { ... }\n reason: { ... }\n required_by: { ... }\n depth: { ... }\n }\n }>\n created_by?: string\n retry_of?: string\n retried_by?: string[]\n plan_only?: boolean\n plan_summary?: {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n conflicts?: number\n }\n planned_at?: string // date-time\n expires_at?: string // date-time\n applied_from_plan?: string\n applied_by?: string[]\n approved_by?: string\n approved_at?: string // date-time\n approvals?: Array<{\n job_id: string\n approved_by?: string\n approved_at: string // date-time\n }>\n errors_sample?: Array<{\n type: string\n source_id: string\n error: string\n }>\n}\n```\n\n### `SyncJobListResponse`\n\nCursor-paginated list of sync jobs.\n\n```ts\ntype SyncJobListResponse = {\n next_cursor?: string\n results: Array<{\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n direction: \"push\" | \"pull\"\n source_org_id: string\n target_org_id: string\n dry_run: boolean\n counts: {\n total: { ... }\n pending: { ... }\n in_progress: { ... }\n succeeded: { ... }\n failed: { ... }\n skipped_unchanged: { ... }\n }\n current_phase?: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n current_batch?: {\n index: { ... }\n of: { ... }\n level: { ... }\n }\n started_at: string // date-time\n finished_at?: string // date-time\n health_check?: {\n status: { ... }\n counts: { ... }\n }\n events?: Array<{\n id?: { ... }\n kind?: { ... }\n seq: { ... }\n ts: { ... }\n phase?: { ... }\n type?: { ... }\n source_id?: { ... }\n target_id?: { ... }\n title?: { ... }\n status: { ... }\n attempt?: { ... }\n message?: { ... }\n error?: { ... }\n dependency?: { ... }\n }>\n created_by?: string\n retry_of?: string\n retried_by?: string[]\n plan_only?: boolean\n plan_summary?: {\n total: { ... }\n create: { ... }\n update: { ... }\n no_change: { ... }\n skip: { ... }\n failed: { ... }\n not_compared: { ... }\n conflicts?: { ... }\n }\n planned_at?: string // date-time\n expires_at?: string // date-time\n applied_from_plan?: string\n applied_by?: string[]\n approved_by?: string\n approved_at?: string // date-time\n approvals?: Array<{\n job_id: { ... }\n approved_by?: { ... }\n approved_at: { ... }\n }>\n errors_sample?: Array<{\n type: { ... }\n source_id: { ... }\n error: { ... }\n }>\n }>\n}\n```\n\n### `SyncJobResource`\n\nPer-resource row backed by `SyncResourcesTable`. See\n`docs/sync/INTERFACES.md` for the DDB shape.\n\n\n```ts\ntype SyncJobResource = {\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: string\n source_id: string\n title?: string\n reason: \"typed_reference\" | \"id_reference\" | \"automation_trigger\" | \"journey_datasource\" | \"file_reference\"\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status?: \"computed\" | \"not_compared\"\n change_count?: number\n changed_paths?: string[]\n note?: string\n depends_on?: Array<{\n type: { ... }\n source_id: { ... }\n }>\n conflicts?: Array<{\n code: { ... }\n certainty: { ... }\n requires_decision: { ... }\n paths: { ... }\n paths_truncated: { ... }\n total_paths: { ... }\n last_synced_at?: { ... }\n target_updated_at?: { ... }\n target_updated_by?: { ... }\n target_updated_by_name?: { ... }\n source_updated_at?: { ... }\n replaced_ratio?: { ... }\n replaced_fields?: { ... }\n total_fields?: { ... }\n }>\n conflict_resolution?: \"overwrite\" | \"keep_target\"\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n}\n```\n\n### `SyncJobResourceListResponse`\n\nCursor-paginated list of sync job resources.\n\n```ts\ntype SyncJobResourceListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n required_by?: {\n type: { ... }\n source_id: { ... }\n title?: { ... }\n reason: { ... }\n }\n status: \"pending\" | \"in_progress\" | \"created\" | \"patched\" | \"skipped\" | \"would_create\" | \"would_patch\" | \"would_skip_unchanged\" | \"would_match_heuristic\" | \"failed\"\n phase: \"phase_0\" | \"dependencies\" | \"phase_a\" | \"phase_a5\" | \"phase_b\" | \"finalize\"\n attempt: number\n error?: string\n updated_at: string // date-time\n source_link?: string\n target_link?: string\n plan?: {\n action: { ... }\n match: { ... }\n diff_status?: { ... }\n change_count?: { ... }\n changed_paths?: { ... }\n note?: { ... }\n depends_on?: { ... }\n conflicts?: { ... }\n conflict_resolution?: { ... }\n }\n excluded_from_apply?: \"unchanged\" | \"excluded\" | \"not_syncable\"\n }>\n}\n```\n\n### `SyncPlanAction`\n\nWhat a real run would do with a planned resource:\n- `create`: no counterpart in the target; a new resource is created.\n- `update`: the target's copy differs from the source and is\n overwritten. Also used when the copies could not be compared\n (`diff_status: not_compared`): such resources are re-ap\n\n```ts\ntype SyncPlanAction = \"create\" | \"update\" | \"no_change\" | \"skip\"\n```\n\n### `SyncPlanMatch`\n\nHow the target counterpart was found: `lineage` (synced before),\n`heuristic` (first sync, matched by name / slug / unique key) or\n`none` (no counterpart: the resource is created).\n\n\n```ts\ntype SyncPlanMatch = \"lineage\" | \"heuristic\" | \"none\"\n```\n\n### `SyncPlanDiffStatus`\n\n`computed` when both copies were read and compared. `not_compared`\nwhen the target could not be read or compared (see `note`).\n\n\n```ts\ntype SyncPlanDiffStatus = \"computed\" | \"not_compared\"\n```\n\n### `SyncResourcePlan`\n\nThe plan for one resource. Set on rows of plan jobs (`dry_run`), and\ncopied onto the rows of the job that applies the plan. Absent on rows\nplanning failed for and on jobs created before plans existed.\n\n\n```ts\ntype SyncResourcePlan = {\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status?: \"computed\" | \"not_compared\"\n change_count?: number\n changed_paths?: string[]\n note?: string\n depends_on?: Array<{\n type: string\n source_id: string\n }>\n conflicts?: Array<{\n code: \"mostly_replaced\" | \"target_edited\" | \"both_edited\" | \"target_only_removed\"\n certainty: \"exact\" | \"possible\"\n requires_decision: boolean\n paths: Array<{\n path: { ... }\n kind: { ... }\n base?: { ... }\n source?: { ... }\n target?: { ... }\n }>\n paths_truncated: boolean\n total_paths: number\n last_synced_at?: string // date-time\n target_updated_at?: string // date-time\n target_updated_by?: string\n target_updated_by_name?: string\n source_updated_at?: string // date-time\n replaced_ratio?: number\n replaced_fields?: number\n total_fields?: number\n }>\n conflict_resolution?: \"overwrite\" | \"keep_target\"\n}\n```\n\n### `SyncPlanConflict`\n\nTarget edits since the last sync that applying the row would\noverwrite. Detection compares normalized plan views (the projection\n`getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit\nfields; references rewritten through lineage; array items matched by\nidentity), never edit dates alone.\n\nW\n\n```ts\ntype SyncPlanConflict = {\n code: \"mostly_replaced\" | \"target_edited\" | \"both_edited\" | \"target_only_removed\"\n certainty: \"exact\" | \"possible\"\n requires_decision: boolean\n paths: Array<{\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n base?: unknown\n source?: unknown\n target?: unknown\n }>\n paths_truncated: boolean\n total_paths: number\n last_synced_at?: string // date-time\n target_updated_at?: string // date-time\n target_updated_by?: string\n target_updated_by_name?: string\n source_updated_at?: string // date-time\n replaced_ratio?: number\n replaced_fields?: number\n total_fields?: number\n}\n```\n\n### `SyncPlanConflictPath`\n\n```ts\ntype SyncPlanConflictPath = {\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n base?: unknown\n source?: unknown\n target?: unknown\n}\n```\n\n### `SyncPlanCounts`\n\nPlanned resources by action. `not_compared` counts the `update`\nresources whose copies could not be compared (it is part of\n`update`, not in addition to it). `failed` counts resources planning\nfailed for.\n\n\n```ts\ntype SyncPlanCounts = {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n conflicts?: number\n}\n```\n\n### `SyncPlan`\n\n```ts\ntype SyncPlan = {\n job_id: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n ready: boolean\n applicable: boolean\n expires_at?: string // date-time\n planned_at?: string // date-time\n replanned_at?: string // date-time\n conflicts_checked_at?: string // date-time\n source_org_id: string\n target_org_id: string\n started_at?: string // date-time\n finished_at?: string // date-time\n summary: {\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n conflicts?: number\n }\n by_type: Array<{\n total: number\n create: number\n update: number\n no_change: number\n skip: number\n failed: number\n not_compared: number\n conflicts?: number\n type: string\n }>\n applied_by?: string[]\n}\n```\n\n### `SyncPlanChange`\n\nOne changed field. `path` uses dots for object keys and\n`[key=value]` for array items that have an identity (`name`, `slug`,\n`key`, `id`, …), e.g. `steps[name=Contact].blocks[name=Email].label`.\n`$` is the whole resource.\n\nValues are compared after removing fields that differ between orgs\nby design:\n\n```ts\ntype SyncPlanChange = {\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n before?: unknown\n after?: unknown\n}\n```\n\n### `SyncPlanDiff`\n\n```ts\ntype SyncPlanDiff = {\n type: string\n source_id: string\n target_id?: string\n title?: string\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status: \"computed\" | \"not_compared\"\n change_count: number\n changes: Array<{\n path: string\n kind: \"added\" | \"removed\" | \"changed\" | \"reordered\"\n before?: unknown\n after?: unknown\n }>\n truncated: boolean\n note?: string\n planned_at?: string // date-time\n}\n```\n\n### `SyncPlanDiffListResponse`\n\n```ts\ntype SyncPlanDiffListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n source_id: string\n target_id?: string\n title?: string\n action: \"create\" | \"update\" | \"no_change\" | \"skip\"\n match: \"lineage\" | \"heuristic\" | \"none\"\n diff_status: \"computed\" | \"not_compared\"\n change_count: number\n changes: Array<{\n path: { ... }\n kind: { ... }\n before?: { ... }\n after?: { ... }\n }>\n truncated: boolean\n note?: string\n planned_at?: string // date-time\n }>\n}\n```\n\n### `SyncPlanApplyRequest`\n\n```ts\ntype SyncPlanApplyRequest = {\n target_auth_token: string\n name?: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n exclude?: Array<{\n type: string\n source_id: string\n }>\n overwrite_conflicts?: Array<{\n type: string\n source_id: string\n }>\n allow_missing_dependencies?: boolean\n}\n```\n\n### `SyncPlanReplanRequest`\n\n```ts\ntype SyncPlanReplanRequest = {\n target_auth_token: string\n resources?: Array<{\n type: string\n source_id: string\n }>\n}\n```\n\n### `SyncPlanMissingDependency`\n\n```ts\ntype SyncPlanMissingDependency = {\n type: string\n source_id: string\n title?: string\n required_by: Array<{\n type: string\n source_id: string\n title?: string\n }>\n}\n```\n\n### `SyncPlanApplyErrorResponse`\n\n```ts\ntype SyncPlanApplyErrorResponse = {\n status?: number\n error?: string\n code?: \"target_auth_token_missing\" | \"empty_selection\" | \"invalid_resources\" | \"plan_not_ready\" | \"plan_expired\" | \"nothing_to_apply\" | \"missing_dependencies\" | \"unresolved_conflicts\"\n message?: string\n invalid_resources?: Array<{\n type: string\n source_id: string\n }>\n missing_dependencies?: Array<{\n type: string\n source_id: string\n title?: string\n required_by: Array<{\n type: { ... }\n source_id: { ... }\n title?: { ... }\n }>\n }>\n resources?: Array<{\n type: string\n source_id: string\n title?: string\n codes: \"target_edited\" | \"both_edited\" | \"target_only_removed\"[]\n }>\n}\n```\n\n### `SyncPlanUnresolvedConflict`\n\n```ts\ntype SyncPlanUnresolvedConflict = {\n type: string\n source_id: string\n title?: string\n codes: \"target_edited\" | \"both_edited\" | \"target_only_removed\"[]\n}\n```\n\n### `DeleteJobStatus`\n\nLifecycle status of a bulk-delete job.\n\n```ts\ntype DeleteJobStatus = \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n```\n\n### `DeleteJobResourceStatus`\n\nPer-resource delete status. `skipped` is used for protected resources\nthat are intentionally not deleted (e.g. built-in schemas).\n\n\n```ts\ntype DeleteJobResourceStatus = \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n```\n\n### `DeleteJobCounts`\n\nAggregate counters by resource delete status.\n\n```ts\ntype DeleteJobCounts = {\n total: number\n pending: number\n in_progress: number\n deleted: number\n skipped: number\n failed: number\n}\n```\n\n### `DeleteJobRequest`\n\nRequest body for `createDeleteJob`.\n\n```ts\ntype DeleteJobRequest = {\n name?: string\n resources: Array<{\n type: string\n id: string\n title?: string\n }>\n}\n```\n\n### `DeleteJob`\n\nBulk-delete job header surfaced by `createDeleteJob`/`getDeleteJob`.\n\n```ts\ntype DeleteJob = {\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n org_id: string\n counts: {\n total: number\n pending: number\n in_progress: number\n deleted: number\n skipped: number\n failed: number\n }\n started_at: string // date-time\n finished_at?: string // date-time\n rebuild_triggered?: boolean\n}\n```\n\n### `DeleteJobListResponse`\n\nCursor-paginated list of delete jobs.\n\n```ts\ntype DeleteJobListResponse = {\n next_cursor?: string\n results: Array<{\n id: string\n name?: string\n status: \"pending\" | \"in_progress\" | \"succeeded\" | \"partial\" | \"failed\" | \"cancelled\"\n org_id: string\n counts: {\n total: { ... }\n pending: { ... }\n in_progress: { ... }\n deleted: { ... }\n skipped: { ... }\n failed: { ... }\n }\n started_at: string // date-time\n finished_at?: string // date-time\n rebuild_triggered?: boolean\n }>\n}\n```\n\n### `DeleteJobResource`\n\nPer-resource row for a delete job.\n\n```ts\ntype DeleteJobResource = {\n type: string\n id: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n attempt: number\n error?: string\n updated_at: string // date-time\n}\n```\n\n### `DeleteJobResourceListResponse`\n\nCursor-paginated list of delete job resources.\n\n```ts\ntype DeleteJobResourceListResponse = {\n next_cursor?: string\n results: Array<{\n type: string\n id: string\n title?: string\n status: \"pending\" | \"in_progress\" | \"deleted\" | \"skipped\" | \"failed\"\n attempt: number\n error?: string\n updated_at: string // date-time\n }>\n}\n```\n\n### `SnapshotInventoryItem`\n\nMinimal identity of a single configuration resource.\n\n```ts\ntype SnapshotInventoryItem = {\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n}\n```\n\n### `SnapshotInventorySkippedType`\n\nA type that could not be listed during inventory collection.\n\n```ts\ntype SnapshotInventorySkippedType = {\n type: string\n reason: string\n}\n```\n\n### `ConfigInventoryResponse`\n\nFresh inventory of all configuration resources for an org.\n\n```ts\ntype ConfigInventoryResponse = {\n inventory_id: string\n org_id: string\n indexed_at: string // date-time\n resources_count: number\n resources: Array<{\n type: \"journey\" | \"datasource\" | \"automation_flow\" | \"workflow_definition\" | \"closing_reason\" | \"flow_template\" | \"schema\" | \"emailtemplate\" | \"product\" | \"price\" | \"tax\" | \"coupon\" | \"file\" | \"document_template\" | \"webhook\" | \"saved_view\" | \"dashboard\" | \"kanban\" | \"role\" | \"usergroup\" | \"validation_rule\" | \"integration\" | \"app\" | \"designbuilder\" | \"notification_template\" | \"custom_variable\" | \"environment_variable\" | \"taxonomy\" | \"taxonomy_classification\" | \"entity_mapping\" | \"portal_config\" | \"target\" | \"product_recommendation\" | \"access_token\" | \"email_signature\" | \"shared_inbox\" | \"org_setting\"\n id: string\n }>\n skipped_types: Array<{\n type: string\n reason: string\n }>\n}\n```\n\n### `CreatePreviewRequest`\n\n```ts\ntype CreatePreviewRequest = {\n name?: string\n requester?: {\n user_id?: string\n email?: string // email\n display_name?: string\n language?: \"de\" | \"en\"\n }\n}\n```\n\n### `PreviewStatus`\n\n- creating_org: the preview org is being created\n- copying_configuration: the sync job copying this org's configuration is running\n- inviting: the requester is being invited to the preview org\n- ready: the requester got the \"your preview org is ready\" email\n- failed: see `error`\n\n\n```ts\ntype PreviewStatus = \"creating_org\" | \"copying_configuration\" | \"inviting\" | \"ready\" | \"failed\"\n```\n\n### `Preview`\n\n```ts\ntype Preview = {\n id: string\n name: string\n status: \"creating_org\" | \"copying_configuration\" | \"inviting\" | \"ready\" | \"failed\"\n source_org_id: string\n preview_org_id?: string\n sync_job_id?: string\n sync_status?: string\n skipped_types?: Array<{\n type: string\n reason: string\n }>\n requested_by: {\n user_id?: string\n email?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n error?: string\n}\n```\n" diff --git a/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts b/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts index c2f722b6..1b345b64 100644 --- a/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts +++ b/packages/epilot-sdk-v2/src/types/configuration-hub.d.ts @@ -309,7 +309,7 @@ export declare namespace Components { } export interface CreatePreviewRequest { /** - * Display name of the preview. Defaults to `Preview `. + * Display name of the preview. Defaults to `Preview`. */ name?: string; /** @@ -652,6 +652,15 @@ export declare namespace Components { * Final status of the copy sync job (`succeeded` or `partial`). */ sync_status?: string; + /** + * Configuration types that could not be listed in the source org (e.g. the + * API answered 403), so the preview has none of them. + * + */ + skipped_types?: { + type: string; + reason: string; + }[]; requested_by: { user_id?: string; email?: string; @@ -1503,6 +1512,10 @@ export declare namespace Components { * When a partial replan (`replanSyncPlan` with `resources`) last finished. */ replanned_at?: string; // date-time + /** + * When conflict detection last ran (end of planning or of a replan). + */ + conflicts_checked_at?: string; // date-time source_org_id: string; target_org_id: string; started_at?: string; // date-time @@ -1523,6 +1536,10 @@ export declare namespace Components { skip: number; failed: number; not_compared: number; + /** + * Rows with at least one conflict (`plan.conflicts`). + */ + conflicts?: number; type: string; }[]; /** @@ -1549,9 +1566,17 @@ export declare namespace Components { /** * Machine-readable reason, for clients that show their own message. */ - code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies"; + code?: "target_auth_token_missing" | "empty_selection" | "invalid_resources" | "plan_not_ready" | "plan_expired" | "nothing_to_apply" | "missing_dependencies" | "unresolved_conflicts"; + /** + * Same as `error` (set with `unresolved_conflicts`). + */ + message?: string; invalid_resources?: SyncJobResourceRef[]; missing_dependencies?: SyncPlanMissingDependency[]; + /** + * With `unresolved_conflicts`, the applied rows with undecided conflicts. + */ + resources?: SyncPlanUnresolvedConflict[]; } export interface SyncPlanApplyRequest { /** @@ -1574,6 +1599,13 @@ export declare namespace Components { * Rows of the plan to leave out. */ exclude?: SyncJobResourceRef[]; + /** + * Rows with conflicts to apply anyway, overwriting the target's + * changes. Applied rows with conflicts that are not listed here + * make the request fail with `unresolved_conflicts`. + * + */ + overwrite_conflicts?: SyncJobResourceRef[]; /** * Apply even when left-out resources that would be created are * referenced by applied ones. Those references then keep pointing @@ -1618,6 +1650,177 @@ export declare namespace Components { */ after?: any; } + /** + * Target edits since the last sync that applying the row would + * overwrite. Detection compares normalized plan views (the projection + * `getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit + * fields; references rewritten through lineage; array items matched by + * identity), never edit dates alone. + * + * With a stored **base** (the target as the last sync left it): + * three-way, `certainty: exact`. Without one: two-way target vs + * source, `certainty: possible`; values only the target has are + * `target_only_removed`, other differences count as `target_edited` + * only when the target was written after the last sync (plus a short + * grace for the sync's own writes). On a first sync (never synced) + * only target-only content is flagged. These three codes are never + * set on heuristic matches. + * + * `mostly_replaced` is independent of match, lineage and base: the + * update would change or remove at least half of the target's leaf + * fields (see `replaced_ratio`), on a target of at least 10 fields. + * Typically a heuristic match to a different resource. It sorts first + * and can come with the other codes on the same row. + * + */ + export interface SyncPlanConflict { + /** + * `mostly_replaced`: applying the row replaces most of the target + * (`replaced_ratio` >= 0.5); check the counterpart is the right + * one. `certainty` is always `exact`, `paths` empty. + * `target_edited`: the target changed these paths since the last + * sync and the source did not; the update reverts them. + * `both_edited`: both changed the same paths, to different values. + * `target_only_removed`: the update removes items only the target + * has (added there since the last sync, or, without a base, not in + * the source). + * + */ + code: "mostly_replaced" | "target_edited" | "both_edited" | "target_only_removed"; + /** + * `exact`: decided from a stored base. `possible`: no base yet. + */ + certainty: "exact" | "possible"; + /** + * Always true for now. `applySyncPlan` refuses the row until it is overwritten or left out. + */ + requires_decision: boolean; + /** + * The affected paths (at most 50). Values are plan-view values; a + * value whose JSON is over 1 KB is replaced by + * `{ "__truncated": true, "json_bytes": }`. Empty when the + * target changed but the copies could not be compared. + * + */ + paths: [ + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath?, + SyncPlanConflictPath? + ]; + /** + * True when paths or values were left out. `total_paths` is still exact. + */ + paths_truncated: boolean; + total_paths: number; + /** + * When Config Hub last synced the resource (lineage), when known. + */ + last_synced_at?: string; // date-time + target_updated_at?: string; // date-time + /** + * User id of the target's last editor, when its API exposes one. + */ + target_updated_by?: string; + /** + * Display name of `target_updated_by`, when resolvable cheaply. Not set yet. + */ + target_updated_by_name?: string; + source_updated_at?: string; // date-time + /** + * `mostly_replaced` only: `replaced_fields / total_fields`, rounded + * to two decimals. Show as a percentage, `round(ratio * 100)`. + * + * example: + * 0.92 + */ + replaced_ratio?: number; + /** + * `mostly_replaced` only: leaf fields of the target the apply + * changes or removes (fields only the source has don't count). + * + * example: + * 198 + */ + replaced_fields?: number; + /** + * `mostly_replaced` only: leaf fields of the target's normalized + * plan view. A scalar array is one field; per-org ids (UUIDs + * lineage doesn't know, the org's own id) are not fields. + * + * example: + * 214 + */ + total_fields?: number; + } + export interface SyncPlanConflictPath { + /** + * Same notation as `SyncPlanChange.path`. + */ + path: string; + /** + * The change applying the row would make to the target. + */ + kind: "added" | "removed" | "changed" | "reordered"; + /** + * The value at the last sync. Absent when unknown (always for `possible`). + */ + base?: any; + /** + * The value applying writes. Absent for `removed`. + */ + source?: any; + /** + * The target's current value. Absent for `added`. + */ + target?: any; + } /** * Planned resources by action. `not_compared` counts the `update` * resources whose copies could not be compared (it is part of @@ -1633,6 +1836,10 @@ export declare namespace Components { skip: number; failed: number; not_compared: number; + /** + * Rows with at least one conflict (`plan.conflicts`). + */ + conflicts?: number; } export interface SyncPlanDiff { type: string; @@ -1744,6 +1951,12 @@ export declare namespace Components { */ resources?: SyncJobResourceRef[]; } + export interface SyncPlanUnresolvedConflict { + type: string; + source_id: string; + title?: string; + codes: ("target_edited" | "both_edited" | "target_only_removed")[]; + } /** * The resource that referenced the dependency. */ @@ -1822,6 +2035,46 @@ export declare namespace Components { * Other resources of the plan this resource references. */ depends_on?: SyncJobResourceRef[]; + /** + * Changes made in the target since the last sync that applying + * this `update` would overwrite, or (`mostly_replaced`) an update + * that would replace most of the target. Absent when there are + * none. Detected while planning from normalized diffs (see + * `SyncPlanConflict`); heuristic matches only get + * `mostly_replaced`. + * + */ + conflicts?: /** + * Target edits since the last sync that applying the row would + * overwrite. Detection compares normalized plan views (the projection + * `getSyncPlanDiff` uses: no own ids, org ids, timestamps or audit + * fields; references rewritten through lineage; array items matched by + * identity), never edit dates alone. + * + * With a stored **base** (the target as the last sync left it): + * three-way, `certainty: exact`. Without one: two-way target vs + * source, `certainty: possible`; values only the target has are + * `target_only_removed`, other differences count as `target_edited` + * only when the target was written after the last sync (plus a short + * grace for the sync's own writes). On a first sync (never synced) + * only target-only content is flagged. These three codes are never + * set on heuristic matches. + * + * `mostly_replaced` is independent of match, lineage and base: the + * update would change or remove at least half of the target's leaf + * fields (see `replaced_ratio`), on a target of at least 10 fields. + * Typically a heuristic match to a different resource. It sorts first + * and can come with the other codes on the same row. + * + */ + SyncPlanConflict[]; + /** + * On rows of a job applying a plan, for rows with conflicts: + * `overwrite` when the row was applied anyway (listed in + * `overwrite_conflicts`), `keep_target` when it was left out. + * + */ + conflict_resolution?: "overwrite" | "keep_target"; } } } @@ -2205,6 +2458,7 @@ export declare namespace Paths { namespace ListSyncJobResources { namespace Parameters { export type Cursor = string; + export type HasConflicts = boolean; export type Id = string; export type PlanAction = /** * What a real run would do with a planned resource: @@ -2236,6 +2490,7 @@ export declare namespace Paths { status?: Parameters.Status; type?: Parameters.Type; plan_action?: Parameters.PlanAction; + has_conflicts?: Parameters.HasConflicts; } namespace Responses { export type $200 = /* Cursor-paginated list of sync job resources. */ Components.Schemas.SyncJobResourceListResponse; @@ -2908,6 +3163,15 @@ export interface OperationMethods { * rejected with 409 and `missing_dependencies`, unless * `allow_missing_dependencies` is set. * + * Rows whose plan has conflicts (`plan.conflicts`: the target was + * changed since the last sync, and applying would overwrite that, or + * applying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway, + * or leave them out (`exclude`, or a `resources` list without them) to + * keep the target's version. Applying one without a decision is + * rejected with 409 and `unresolved_conflicts`; nothing is applied. + * The decision is recorded on the apply job's rows + * (`plan.conflict_resolution`). + * * Applied resources go through the normal sync phases, so a change in * the target since planning is handled like in any sync. A plan can be * applied up to 24 hours after it finished (`expires_at`); plan again @@ -3578,6 +3842,15 @@ export interface PathsDictionary { * rejected with 409 and `missing_dependencies`, unless * `allow_missing_dependencies` is set. * + * Rows whose plan has conflicts (`plan.conflicts`: the target was + * changed since the last sync, and applying would overwrite that, or + * applying would replace most of the target) need a decision: list them in `overwrite_conflicts` to apply them anyway, + * or leave them out (`exclude`, or a `resources` list without them) to + * keep the target's version. Applying one without a decision is + * rejected with 409 and `unresolved_conflicts`; nothing is applied. + * The decision is recorded on the apply job's rows + * (`plan.conflict_resolution`). + * * Applied resources go through the normal sync phases, so a change in * the target since planning is handled like in any sync. A plan can be * applied up to 24 hours after it finished (`expires_at`); plan again @@ -3818,6 +4091,8 @@ export type SyncPlanAction = Components.Schemas.SyncPlanAction; export type SyncPlanApplyErrorResponse = Components.Schemas.SyncPlanApplyErrorResponse; export type SyncPlanApplyRequest = Components.Schemas.SyncPlanApplyRequest; export type SyncPlanChange = Components.Schemas.SyncPlanChange; +export type SyncPlanConflict = Components.Schemas.SyncPlanConflict; +export type SyncPlanConflictPath = Components.Schemas.SyncPlanConflictPath; export type SyncPlanCounts = Components.Schemas.SyncPlanCounts; export type SyncPlanDiff = Components.Schemas.SyncPlanDiff; export type SyncPlanDiffListResponse = Components.Schemas.SyncPlanDiffListResponse; @@ -3825,5 +4100,6 @@ export type SyncPlanDiffStatus = Components.Schemas.SyncPlanDiffStatus; export type SyncPlanMatch = Components.Schemas.SyncPlanMatch; export type SyncPlanMissingDependency = Components.Schemas.SyncPlanMissingDependency; export type SyncPlanReplanRequest = Components.Schemas.SyncPlanReplanRequest; +export type SyncPlanUnresolvedConflict = Components.Schemas.SyncPlanUnresolvedConflict; export type SyncRequiredBy = Components.Schemas.SyncRequiredBy; export type SyncResourcePlan = Components.Schemas.SyncResourcePlan;